2.7 表和Schema创建
有几个ORMLite提供的工具,可以帮助你为存入数据库的类创建表和schema。
2.7.1 TableUtils类
TableUtils类提供了一些静态方法用以辅助创建和删除表,同样也提供了schema申明。(下面例举出静态方法名,详细说明参见官网)
静态方法原型 |
createTable(ConnectionSource, Class) |
createTableIfNotExists(ConnectionSource, Class) |
createTable(ConnectionSource, DatabaseTableConfig) |
createTableIfNotExists(ConnectionSource, DatabaseTableConfig) |
dropTable(ConnectionSource, Class, boolean ignoreErrors) |
dropTable(ConnectionSource, DatabaseTableConfig, boolean ignoreErrors) |
getCreateTableStatements(ConnectionSource, Class) |
getCreateTableStatements(ConnectionSource, DatabaseTableConfig) |
clearTable(ConnectionSource, Class) |
clearTable(ConnectionSource, DatabaseTableConfig) |
2.7.2 TableCreator类
TableCreator这个类虽然是为使用Spring框架设计的,但是在其他的配置方面是很有用的。它配置ConnectionSource和被程序使用的DAO列表。
如果系统属性ormlite.auto.create.tables设置成true值,他会自动创建和这些DAO相关的表。如果系统属性ormlite.auto.drop.tables设置成true值,它也会自动删除创建的表。这在测试的时候特别有用:你开始使用取得最新schema的测试数据库,但是实际生产过程中你需要手动改变一个具体的schame。你可以在你运行测试脚本的时候设置系统属性,但是在运行实际脚本时要关闭它。
List<Dao<?, ?>> daoList = new ArrayList<Dao<?, ?>>();
daoList.add(accountDao);
...
TableCreator creator =
new TableCreator(connectionSource, daoList);
// create the tables if the right system property is set
creator.maybeCreateTables();
...
// later, we may want to drop the tables that were created
creator.maybeDropTables();
2.8 唯一标识符字段
数据库中的记录通过定义为唯一的特殊字段成为唯一标识。记录不是必须有唯一标识字段当时很多DAO操作(更新、删除、刷新)都需要一个唯一标识字段。这个标识要么用户提供要么数据库自动生成。标识字段有表中唯一的值并且如果你用DAO根据id查询、删除、刷新或者更新指定行的时候他们必须存在。为了配置一个成员变量作为标识成员,你应该使用下面三个设置之一(而且必须使用一个):@DatabaseField: id, generatedId, generatedIdSequence 。
2.8.1 成员变量使用id
用我们的Account类的示例,字符串name变量被标记有id = true 。这意味着name变量是这个对象的标识字段。每个account存入数据库都必须有一个唯一的name变量值,比如你不能有两行的name都是“John Smith”。
public class Account {
@DatabaseField(id = true)
private String name;
...
}
当你使用DAO利用具体的name值查询时,你将使用标识成员定位数据库中
的Account对象。
Account account = accountDao.queryForId("John Smith");
if (account == null) {
// the name "John Smith" does not match any rows
}
注意:如果你需要改变对象id字段的值,那么你必须使用Dao.updateId()方法,它获得当前对象的id旧值和新值。
2.8.2 成员变量使用generatedId
你可以配置一个long或integer的变量作为生成的标识字段。每条记录的id号都是数据库自动生成的。
public class Order {
@DatabaseField(generatedId = true)
private int id;
...
}
传递一个Order对象去创建和存储到数据库时,数据库返回一个生成的id值并且ORMLite设置给对象。在大部分数据库类型中向表中插入一条新记录时生成的值从1开始,每次增长1。
// build our order object without an id
Order order = new Order("Jim Sanders", 12.34);
...
orderDao.create(order);
System.out.println("Order id " + order.getId() +
" was persisted to the database");
// query for the order with an id of 1372
order = orderDao.queryForId(1372);
if (order == null) {
// none of the order rows have an id of 1372
}
在上面的代码示例中,一个order构造用name和amount两个属性。当把它传给DAO'的create方法时,id变量没有设置。它保存到数据库之后,ORMLite会把生成的id设置给id变量并且getId()方法在create方法返回后被order调用是有有效的。
注意:其他特殊变量类型也是可以生成的,比如UUID。你可以使用allowGeneratedIdInsert变量进行设置,允许向表中插入拥有已经设置过或没有设置过id的对象。根据数据库类型,你可能不能改变自动生成id字段的值。
2.8.3 成员变量使用generatedIdSequence
一些数据库使用一种被称为序列号生成器的东西来提供生成id的值。如果你把generatedId = true用在这些数据库上,序列名将会被ORMLite自动生成。如果这样,你需要设置序列名来匹配已经存在的schema,你可以使用generatedIdSequence序列名的值。
public class Order {
@DatabaseField(generatedIdSequence = "order_id_seq")
private int id;
...
}
在上面的示例中,虽然id值再次自动生成,但是仍然使用序列名:order_id_seq 。如果你使用的数据库不支持序列,那么这将会抛出一个异常。
注意:根据数据库类型,你不能改变自动生成id字段的值。
2.9 DAO 的使用
下面通过使用DAO方法简单完成数据库操作:
①创建并且持久化对象到数据库。
插入一条和对象相关的记录到数据库中。
Account account = new Account();
account.name = "Jim Coakley";
accountDao.create(account);
②查询它的id字段
如果对象有个id成员变量通过注解定义的,我们可以通过它的id在数据库中查找一个对象。
Account account = accountDao.queryForId(name);
if (account == null) {
account not found handling ...
}
③更新与对象相关的数据库记录
如果你在内存中改变一个对象的成员变量,你必须调用update把它持久化到数据库。这需要一个id字段。
account.password = "_secret";
accountDao.update(account);
④当数据库有改变,刷新对象
如果一些与内存中对象相关的数据库实体发生了改变,你就需要刷新来得到最新的存储对象。这需要一个id字段。
accountDao.refresh(account);
⑤从数据库中删除数据
从数据库删除与对象关联的记录。一旦对象从数据库删除,你可以继续使用内存中的对象但是任何的更新或者刷新都很可能失败。这需要一个id字段。
accountDao.delete(account);
⑥遍历表中所有记录
DAO也有迭代器,所以你可以简单的执行数据库中所有的记录。
// page through all of the accounts in the database
for (Account account : accountDao) {
System.out.println(account.getName());
}
注意:你必须遍历迭代器所有的条目来关闭底层的SQL对象。如果你没有通过循环走所有的途径,那么ORMLite不知道关闭底层对象,并且一个到数据库的连接可能泄露,如果更迟一些垃圾回收器才获得它,那么它将被迫关闭,这会在你的代码中产出漏洞。使用下面的try ... finally模板包住迭代器。
例如,下面是一个不好的循环模板。
for (Account account : accountDao) {
if (account.getName().equals("Bob Smith")) {
// you can't return, break, or throw from here
return account;
}
}
如果一个异常仍出循环这种bug照样会发生,所以如果这样的话循环就不应该被使用。这也是一个用迟加载收集的一个案例。
⑦直接使用迭代器
你也可以直接使用迭代器,因为用循环并不是最佳选择。这种方式允许你使用更好的try ... finally模板。
CloseableIterator<Account> iterator =
accountDao.closeableIterator();
try {
while (iterator.hasNext()) {
Account account = iterator.next();
System.out.println(account.getName());
}
} finally {
// close it at the end to close underlying SQL statement
iterator.close();
}
⑧获得"wrapped iterable"
你也可以使用"wrapped iterable",它允许你在finally中使用close而一直使用循环。
CloseableWrappedIterable<Account> wrappedIterable =
accountDao.getWrappedIterable();
try {
for (Account account : wrappedIterable) {
...
}
} finally {
wrappedIterable.close();
}
2.10 索引成员
在你的数据类中ORMLite提供了一些多种成员索引有限的支持。首先,它重点指明任何已经被标记成id的成员变量已经被编入索引。一个id成员变量不需要添加额外构建的索引并且如果他们被指定的话那么数据库会产生错误。
添加一个索引到没有id的成员变量,你需要添加index = true布尔域到@DatabaseField注解。这将会在表被创建时创建一个非唯一的索引给成员变量并且如果表被删除那么将删除索引。索引用于帮助优化查询并且在查询媒介中数据量大的表时显著优化了查询时间。
public class Account {
@DatabaseField(id = true)
private String name;
// this indexes the city field so queries on city
// will go faster for large tables
@DatabaseField(index = true)
private String city;
...
}
这个例子在Account表中创建一个account_city_idx索引。如果你想用不同的名字,你可以使用indexName = "othername",用允许你指定的索引名来替换othername成员。
@DatabaseField(indexName = "account_citystate_idx")
private String city;
@DatabaseField(indexName = "account_citystate_idx")
private String state;
这个示例会为city和state成员变量都创建一个索引。注意,通过city本身查询是没有优化的,只有在city和state多关键字查询时才会被优化。有些数据库,它可能更好的创建一个单一字段索引在每个字段上而且如果你用city和state多关键字查询时它会让数据库同时使用两个索引。对于另一些数据库,推荐在多个成员变量上创建一个索引。你可能需要尝试使用SQL EXPLAIN命令来查明你的数据库是怎么使用你的索引的。
创建一个唯一的索引,uniqueIndex = true和uniqueIndexName ="othername"成员变量仅仅在@DatabaseField注解中有效。这些操作和上面的设置一样但是将会不用创建唯一索引来确保没有任何两条记录的索引有相同的值。
2.11 发出原生SQL语句
在大量实例中,使用DAO定义的功能操作数据库可能还不够。由于这个原因,ORMLite允许你发出查找、更新、执行等数据库原生语句给数据库。
2.11.1 发出原生查找
通过Dao接口的内置方法并且QueryBuilder类没有提供操作所有查询类型的能力。比如,聚合查询(sum,count,avg等等)不能当做一个对象进行操作,因为每个查询有不同的结果列表。为了这样的查询操作,你可以使用DAO中的queryRaw方法发出原生的数据库查询。这些方法返回一个GenericRawResults对象,它表示一个结果是一个字符串数组,对象数组或者用户映射对象。查看关于GenericRawResults的文档有更多如何使用它的详解说明,或者看看下面的示例。
// find out how many orders account-id #10 has
GenericRawResults<String[]> rawResults =
orderDao.queryRaw(
"select count(*) from orders where account_id = 10");
// there should be 1 result
List<String[]> results = rawResults.getResults();
// the results array should have 1 value
String[] resultArray = results.get(0);
// this should print the number of orders that have this account-id
System.out.println("Account-id 10 has " + resultArray[0] + " orders");
你甚至可以使用QueryBuilder构建原生的查询,如果你喜欢使用prepareStatementString()方法的话。
QueryBuilder<Account, Integer> qb = accountDao.queryBuilder();
qb.where().ge("orderCount", 10);
results = accountDao.queryRaw(qb.prepareStatementString());
如果你想以参数的形式使用QueryBuilder原生查询,那么你应该像这样的:
QueryBuilder<Account, Integer> qb = accountDao.queryBuilder();
// we specify a SelectArg here to generate a ? in statement string below
qb.where().ge("orderCount", new SelectArg());
// the 10 at the end is an optional argument to fulfill SelectArg above
results = accountDao.queryRaw(qb.prepareStatementString(), 10);
如果你想以聚合的方式使用QueryBuilder或者是其他原生、自定义的参数那么像下面这样做。因为只有一个结果输出你可以使用genericRawResults.getFirstResult()方法:
QueryBuilder<Account, Integer> qb = accountDao.queryBuilder();
// select 2 aggregate functions as the return
qb.selectRaw("MIN(orderCount)", "MAX(orderCount)");
// the results will contain 2 string values for the min and max
results = accountDao.queryRaw(qb.prepareStatementString());
String[] values = results.getFirstResult();
对于有大量的结果集,你可以考虑使用利用数据库分页的GenericRawResults对象的the iterator()方法。示例:
// return the orders with the sum of their amounts per account
GenericRawResults<String[]> rawResults =
orderDao.queryRaw(
"select account_id,sum(amount) from orders group by account_id");
// page through the results
for (String[] resultArray : rawResults) {
System.out.println("Account-id " + resultArray[0] + " has "
+ resultArray[1] + " total orders");
}
rawResults.close();
如果你传进去的结果字段类型有些字段不能合适的映射到字符串,你也可以以Object[]形式返回字段。例如:
// return the orders with the sum of their amounts per account
GenericRawResults<Object[]> rawResults =
orderDao.queryRaw(
"select account_id,sum(amount) from orders group by account_id",
new DataType[] { DataType.LONG, DataType.INTEGER });
// page through the results
for (Object[] resultArray : rawResults) {
System.out.println("Account-id " + resultArray[0] + " has "
+ resultArray[1] + " total orders");
}
rawResults.close();
注意:select * 能返回在不同的orders表中的字段,这依赖于数据库类型。
为了保证数组数据类型和返回的列匹配,你必须具体地指定字段并且不能用SQL中的 * 。
你也可以通过在RawRowMapper对象传一个你自己的对象来映射结果集。这将调用对象和一个字符串数组的映射并且它把字符串转化为对象。例如:
// return the orders with the sum of their amounts per account
GenericRawResults<Foo> rawResults =
orderDao.queryRaw(
"select account_id,sum(amount) from orders group by account_id",
new RawRowMapper<Foo>() {
public Foo mapRow(String[] columnNames,
String[] resultColumns) {
return new Foo(Long.parseLong(resultColumns[0]),
Integer.parseInt(resultColumns[1]));
}
});
// page through the results
for (Foo foo : rawResults) {
System.out.println("Account-id " + foo.accountId + " has "
+ foo.totalOrders + " total orders");
}
rawResults.close();
注意:查询和结果字符串可以是非常具体的数据库类型。比如:
1、某一数据库需要一些字段名指定成大写,另一些需要指定成小写。
2、你必须引用你的字段名或表明,如果他们是关键字的话。
3、结果集字段名也可以是大写或者是小写。
4、Select * 可以根据数据库类型返回orders表中不同的字段。
注意:就像其他的ORMLite迭代器,你将需要确定循环遍历所以结果集后有自动关闭的申明。你也可以调用GenericRawResults.close()方法来确保迭代器和其他相关数据库连接被关闭。
2.11.2 发出原生更新语句
如果DAO给你的功能不够灵活的话,你也可以发出数据的原生更新语句。更新的SQL语句必须包含关键字INSERT,、DELETE、 UPDATE。例如:
fooDao.updateRaw("INSERT INTO accountlog (account_id, total) "
+ "VALUES ((SELECT account_id,sum(amount) FROM accounts))
2.11.3 发出原生的执行语句
如果DAO给你的功能不够灵活的话,你也可以发出数据的原生更新语句。例如:
fooDao.executeRaw("ALTER TABLE accountlog DROP COLUMN partner");
2.12 外部对象字段
ORMLite支持"foreign"对象的概念,一个或多个与对象相关的字段被持久化到同一数据库的另一张表中。比如,如果在你的数据库中有一个order对象, 并且每个order有一个对应的Account对象,那么这个order对象就会有外部Account字段。有一个外部对象,只有Account中的id字段被持久化到order表中的account_id列。例如,这个order类可以像这样:
@DatabaseTable(tableName = "orders")
public class Order {
@DatabaseField(generatedId = true)
private int id;
@DatabaseField(canBeNull = false, foreign = true)
private Account account;
...
}
当order表被创建时,有些像下面的SQL将会被生产:
CREATE TABLE `orders`
(`id` INTEGER AUTO_INCREMENT , `account_id` INTEGER,
PRIMARY KEY (`id`));
注意:字段名不是account,而是account_id。如果你查询的时候你将会使用这个字段名。你可以在DatabaseField注解中使用columnName成员来设置字段名。
当你用外部对象创建一个字段时,请注意这个外键对象不会为你自动创建。如果你的外部对象有一个数据库提供的generated-id,那么你需要在你创建其他引用它的对象之前创建它。例如:
Account account = new Account("Jim Coakley");
accountDao.create(account);
// this will create the account object and set any generated ids
// now we can set the account on the order and create it
Order order = new Order("Jim Sanders", 12.34);
order.setAccount(account);
...
orderDao.create(order);
如果你希望一些自动创建的等级,那么你可以使用foreignAutoCreate进行设置。
当你查询一个order表时,你将会得到一个Order对象,这对象拥有一个有它id集合的account字段。在外部Account对象中剩下的字段将有默认值(null,0,false等)。如果你想使用Account中的其他字段,你必须调用accountDao类的refresh来得到填充了的Account对象。比如:
Order order = orderDao.queryForId(orderId);
System.out.println("Account-id on the order should be set: " +
order.account.id);
// this should print null for order.account.name
System.out.println("But other fields on the account should not be set: "
+ order.account.name);
// so we refresh the account using the AccountDao
accountDao.refresh(order.getAccount());
System.out.println("Now the account fields will be set: " +
order.account.name);
你可以通过使用foreignAutoRefresh设置拥有一个自动刷新的外部对象。
注意:因为我们使用refresh,所以外部对象需要有一个id字段。
你可以用两三种不同的方式查询外部字段。下面实例演示代码,代码是查询所有匹配确定的account字段的所有order。因为id字段是name字段,所有你可以通过account的name字段来进行查询。
// query for all orders that match a certain account
List<Order> results =
orderDao.queryBuilder().where().
eq("account_id", account.getName()).query();
或者你可以仅仅让ORMLite从account取得id字段。这将演示一个和上面等同的查询:
// ORMLite will extract and use the id field internally
List<Order> results =
orderDao.queryBuilder().where().
eq("account_id", account).query();
2.13 外部集合
在本手册前面章节中我们有个Order类的例子,它有一个到Account表的外部对象字段。一个外部集合允许你添加account表中的orders集合。每当Account对象通过DAO的查询或刷新返回时,order表和设置在account上orders集合规定了一个单独的查询。所有的orders在集合中有一个对应的和account匹配的外部对象。例如:
public class Account {
...
@ForeignCollectionField(eager = false)
ForeignCollection<Order> orders;
...
}
在上面的示例中,@ForeignCollectionField注解标记了orders成员变量是一个匹配account的orders集合。成员变量orders的类型必须要么是ForeignCollection<T>要么是Collection<T>,没有其他的集合被支持,因为其他集合难以有更多的方法支持。@ForeignCollectionField注解支持下面的成员:
成员名 |
eager |
maxEagerLevel |
columnName |
orderColumnName |
foreignFieldName |
备注:具体成员描述参见官方文档。
记住,当你有个ForeignCollection成员变量,集合中的类必须得有一个外部成员。如果Account有个Orders的外部集合,那么Order必须有一个Account外部成员。它是这么要求的,所以ORMLite能找到匹配具体account的orders。
警告:用lazy集合甚至是size()方法导致迭代器跨越数据库。你可能最想只使用lazy集合中的iterator() 和toArray()方法。
注意:就像使用Dao.iterator()方法类似,迭代器被lazy集合返回,当你用了它那么必须关闭它,因为有链接在数据库底层一直开着。下面的方式关闭操作会执行:那么是你通过迭代器把所有的方式走一遍,那么是你调用close()方法。只有ForeignCollection会返回一个可以关闭的迭代器。这意味着循环懒加载集合是不好的模式。
在这种情况下外部集合支持add()和remove()方法:如果一个集合想对象被添加和从内部列表删除,并且DAO被调用用来影响order表以及eager和lazy集合。
注意:当你在一个使用了外部集合的对象上调用upate时,保存在集合中的对象不是自动写到数据库的。可惜在ORMLite中没有方法可以检测到对象被更新了。如果你更新一个集合中的对象你需要在ForeignCollection上调用update(data)方法来确保对象被持久化。例如:
for (Order order : account.orders()) {
// if we are changing some field in the order
order.setAmount(123);
// then we need to update it in the database
account.orders.update(order);
}
2.14 DAO激活对象
另一种ORM模式是:有对象执行和他们自己相关的数据库操作来代替使用DAO。比如,给一个数据对象foo,你会调用foo.refresh()来代替fooDao.refresh(foo)。默认的模式是使用DAO类,它允许你的数据类有他们自己的层次并且它独立于Daos中的数据库代码。但是,如果你喜欢这种模式的话你可以自由使用BaseDaoEnabled类。
要使所有的类能够刷新(更新、删除等等)他们自己,那么需要继承BaseDaoEnabled类。例如:
@DatabaseTable(tableName = "accounts")
public class Account extends BaseDaoEnabled {
@DatabaseField(id = true)
private String name;
@DatabaseField(canBeNull = false)
private String password;
...
首先创建对象,你需要使用DAO对象或者你需要设置相关对象的dao以便它能自我创建:
account.setDao(accountDao);
account.create();
不过,任何时候一个对象被ORMLite作为一个查询结果返回,那么DAO已经被设置在继承BaseDaoEnabled类的对象上了。
Account account = accountDao.queryForId(name);
account.setPassword(newPassword);
account.update();
这也将会为外部成员工作。
Order order = orderDao.queryForId(orderId);
// load all of the fields from the account
order.getAccount().refresh();
这个BaseDaoEnabled文档有最新的操作列表,现在类仅仅可以做:
操作名称 | 描述 |
create | 创建对象,你需要使用DAO或者在对象上调用setDao()。 |
refresh | 当数据库中数据发生更新时刷新对象。 |
update | 你改变了内存中的对象之后把它更新到数据库。 |
updateId | 如果你需要更新对象的ID那么你需要使用这个方法。你不能改变对象的id成员然后调用更新方法,因为这样对象会找不到。 |
delete | 从数据库删除。
|