Android数据库ORMlite框架04
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 |
从数据库删除。
|