前言
這一部分繼續Android數據庫ORMlite框架翻譯系列(第二章:part 2)部分。這一次的翻譯距離part2部分還是有一下段時間,文章很多東西英文可以理解但是非要用中文來表達還是挺不容易的。
首先還是建議參考英文文檔,本文僅作參考。如果有翻譯的不妥之處請讀者提出,謝謝。
另外,本系列的文章好像並不被大家看好,所以這篇文章也將是本系列的最后一篇。如果以后有必要的話再堅持把剩下的部分翻譯完。
-------------------------------------------------------------------------------------
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 |
從數據庫刪除。 |
PS:本章內容到此為止。這部分也是整個ORMLite框架的重點。
轉載請注明出處。歡迎交流。