GreenDao3.0系列文章:
GreenDao3.0 源碼分析-DaoMaster和DaoSeesion
Dao 是GreenDao進(jìn)行數(shù)據(jù)查詢的一層,起到非常重要的作用,今晚我們就來(lái)聊聊GreenDao是如何做增刪改查的吧。
Order實(shí)體
我們從稍微復(fù)雜的Order進(jìn)行分析,去除自動(dòng)生成的代碼,源實(shí)體是:
public class Order {
@Id
private Long id;
private java.util.Date date;
private long customerId;
@ToOne(joinProperty = "customerId")
private Customer customer;
}
如上圖,Order和Customer對(duì)象是一對(duì)一的關(guān)系,下面我們來(lái)看看生成的OrderDao:
OrderDao
以下幾點(diǎn)是我歸納的要點(diǎn)
1、所有的實(shí)體DAO對(duì)象都繼承自 AbstractDao<Order, Long>。
2、TABLENAME常量定義了數(shù)據(jù)庫(kù)表名,默認(rèn)為實(shí)體類名大寫(xiě),nameInDb 可以自定義表名。
3、每個(gè)實(shí)體Dao都有一個(gè)Properties來(lái)管理對(duì)象實(shí)體屬性對(duì)表各列的映射關(guān)系,對(duì)像為Property。
4、提供創(chuàng)建表和刪除表的實(shí)現(xiàn)。
5、提供Statement綁定對(duì)應(yīng)實(shí)例的方法。
6、讀取Cursor轉(zhuǎn)化成對(duì)象。
7、獲得主鍵。
以上是最基礎(chǔ)的操作,是必須的,還有因?yàn)橐恍┨厥獾模?/p>
8、當(dāng)Java實(shí)體需要轉(zhuǎn)成其他類型,比如String存儲(chǔ)時(shí),需要提供一個(gè)轉(zhuǎn)化器PropertyConverter
9、為了查詢告訴,把實(shí)體Id設(shè)置成RowId
10、還有就是一對(duì)多關(guān)系,創(chuàng)建的一些關(guān)聯(lián)性代碼。
上面歸納的就是實(shí)體Dao提供的功能,下面我們逐步對(duì)代碼進(jìn)行解析:
1、2、3我就不說(shuō)了,非常簡(jiǎn)單,就是通過(guò) Property屬性對(duì)象來(lái)進(jìn)行管理,每一個(gè)Property就是對(duì)象數(shù)據(jù)庫(kù)的一列。
3、4是數(shù)據(jù)庫(kù)的基本操作,通過(guò)Database數(shù)據(jù)庫(kù)對(duì)象執(zhí)行Sql語(yǔ)句創(chuàng)建表和刪除表,每次創(chuàng)建刪除是通過(guò)DaoMaster進(jìn)行操作的,兩個(gè)方法都是靜態(tài)方法。
我們來(lái)簡(jiǎn)單說(shuō)下5:
private final Date2LongConver dateConverter = new Date2LongConver();
protected final void bindValues(SQLiteStatement stmt, Order entity) {
stmt.clearBindings();
Long id = entity.getId();
if (id != null) {
stmt.bindLong(1, id);
}
Date date = entity.getDate();
if (date != null) {
stmt.bindLong(2, dateConverter.convertToDatabaseValue(date));
}
stmt.bindLong(3, entity.getCustomerId());
}</pre>
SQLiteStatement是我們定義好的一些增刪改查語(yǔ)句的聲明,通過(guò)以?來(lái)做占位符,達(dá)到提供性能的目的,這里就是把Order實(shí)例的數(shù)據(jù)信息,按照序號(hào),進(jìn)行綁定到SQLiteStatement中,以供數(shù)據(jù)庫(kù)做查詢等操作,從上面源碼我們還能看到,GreenDao的轉(zhuǎn)化器,其實(shí)就是按照一定的規(guī)則,生成對(duì)映的Date2LongConver dateConverter = new Date2LongConver();對(duì)象,然后執(zhí)行方法,達(dá)到轉(zhuǎn)換的母目的。
下面我們看看讀對(duì)象的操作:
public Order readEntity(Cursor cursor, int offset) {
Order entity = new Order( //
cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id
cursor.isNull(offset + 1) ? null : dateConverter.convertToEntityProperty(cursor.getLong(offset + 1)), // date
cursor.getLong(offset + 2) // customerId
);
return entity;
}
讀的操作也是非常簡(jiǎn)單,通過(guò)判空后進(jìn)行賦值,相應(yīng)的需要轉(zhuǎn)化的對(duì)象也會(huì)轉(zhuǎn)化。
7、8、9各位看官自己查看,比較簡(jiǎn)單getKey(Order entity)獲取主鍵,updateKeyAfterInsert(Order entity, long rowId)是插入成功后更換Key,hasKey(Order entity)判斷是否有主鍵。
下面我們說(shuō)說(shuō),關(guān)于GreenDao是如何實(shí)現(xiàn)一對(duì)多的問(wèn)題:
我們?cè)賮?lái)引入一個(gè)類Customer,Customer類和Order是一對(duì)多的關(guān)系
@ToMany(joinProperties = {
@JoinProperty(name = "id", referencedName = "customerId")
})
@OrderBy("date ASC")
private List<Order> orders;</pre>
GreenDao處理一對(duì)多的關(guān)系,是通過(guò)取得OrderDao的引用來(lái)進(jìn)行查詢:
public List<Order> getOrders() {
if (orders == null) {
final DaoSession daoSession = this.daoSession;
if (daoSession == null) {
throw new DaoException("Entity is detached from DAO context");
}
OrderDao targetDao = daoSession.getOrderDao();
List<Order> ordersNew = targetDao._queryCustomer_Orders(id);
synchronized (this) {
if (orders == null) {
orders = ordersNew;
}
}
}
return orders;
}
id就是customerId,我們?cè)倏纯確queryCustomer_Orders這里方法:
public List<Order> _queryCustomer_Orders(long customerId) {
synchronized (this) {
if (customer_OrdersQuery == null) {
QueryBuilder<Order> queryBuilder = queryBuilder();
queryBuilder.where(Properties.CustomerId.eq(null));
queryBuilder.orderRaw("T.'DATE' ASC");
customer_OrdersQuery = queryBuilder.build();
}
}
Query<Order> query = customer_OrdersQuery.forCurrentThread();
query.setParameter(0, customerId);
return query.list();
}</pre>
思路已經(jīng)很清晰了,就通過(guò)獲取多對(duì)象的Dao引用,進(jìn)行查詢操作,因?yàn)檫@里還添加時(shí)間的排序,所以添加增加了時(shí)間排序,一對(duì)一的關(guān)系也大致如此。
Dao已經(jīng)說(shuō)完了,接下來(lái)我們來(lái)進(jìn)行AbstractDao的解析
AbstractDao
AbstractDao封裝了和數(shù)據(jù)庫(kù)進(jìn)行的增刪改查功能。
大致功能如下圖:
AbstractDao提供了插入、更新、刪除、保存,查詢等功能,額為功能還包括對(duì)Rx1.0的適配,統(tǒng)計(jì)表的行數(shù)等。
因?yàn)椴僮黝愃?,我們這里只分析插入部分,其他部分可通過(guò)自己閱讀完成理解:
可以看到上面的思維導(dǎo)圖。子樹(shù)是面對(duì)用戶的API,最終單個(gè)實(shí)體插入會(huì)執(zhí)行到insertInsideTx,而多數(shù)據(jù)實(shí)體會(huì)執(zhí)行到executeInsertInTx。
這里我來(lái)理一下思路,GreenDao不管是做查詢還是其他操作都是使用Statement來(lái)進(jìn)行優(yōu)化性能的,而且Statement是可以重用的,所以GreenDao有自己的Statement管理類,就是TableStatements,我們來(lái)看看TableStatements對(duì)插入聲明的創(chuàng)建:
public DatabaseStatement getInsertStatement() {
if (insertStatement == null) {
String sql = SqlUtils.createSqlInsert("INSERT INTO ", tablename, allColumns);
DatabaseStatement newInsertStatement = db.compileStatement(sql);
synchronized (this) {
if (insertStatement == null) {
insertStatement = newInsertStatement;
}
}
if (insertStatement != newInsertStatement) {
newInsertStatement.close();
}
}
return insertStatement;
}
從上面代碼我們知道,TableStatements維護(hù)著一個(gè)insertStatement對(duì)象,如果不為null就直接返回,為null就拼接創(chuàng)建,以達(dá)到復(fù)用優(yōu)化性能的作用,這是數(shù)據(jù)庫(kù)常見(jiàn)的操作,SqlUtils工具類是Sql語(yǔ)句拼接的工具,大家有興趣自己看一下。
我們來(lái)先聊聊單個(gè)實(shí)體做插入的時(shí)候,從面向用戶的API獲取到想用的插入,或者插入或替換的Statement后,插入操作會(huì)執(zhí)行
executeInsert(T entity, DatabaseStatement stmt, boolean setKeyAndAttach)方法:
private long executeInsert(T entity, DatabaseStatement stmt, boolean setKeyAndAttach) {
long rowId;
//先判斷當(dāng)前線程是否連接了數(shù)據(jù)庫(kù)
if (db.isDbLockedByCurrentThread()) {
//返回true 直接插入數(shù)據(jù)
rowId = insertInsideTx(entity, stmt);
} else {
//在鎖定stmt之前通過(guò)開(kāi)啟transation請(qǐng)求連接
db.beginTransaction();
try {
rowId = insertInsideTx(entity, stmt);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
if (setKeyAndAttach) {
updateKeyAfterInsertAndAttach(entity, rowId, true);
}
return rowId;
}</pre>
到這里 插入步驟如下:
1、通過(guò)判斷當(dāng)前線程是否和數(shù)據(jù)庫(kù)關(guān)聯(lián)來(lái)決定是否需要開(kāi)啟事務(wù)。
2、最后都執(zhí)行insertInsideTx,里面的操作很簡(jiǎn)單就是調(diào)用之前子類的bindValue方法進(jìn)行綁定值后執(zhí)行Sql操作。
3、插入后的善后處理,這里就是更新實(shí)體的ID為RowId和做內(nèi)存緩存,還有一些特殊操作實(shí)體綁定DaoSeesion,使用active = true會(huì)用到。
我再來(lái)看看executeInsertInTx,獲取到Statement類似,因?yàn)槭嵌鄶?shù)據(jù)插入,強(qiáng)制使用事務(wù):
這里我們?cè)僖胍粋€(gè)概念,就是IdentityScope<K, T>是GreenDao用來(lái)做內(nèi)存緩存的,可以看成是一個(gè)Map,如果是Long,GreenDAO做了對(duì)應(yīng)的優(yōu)化,因?yàn)槎鄶?shù)據(jù)插入是比較耗時(shí)的,所以,我們執(zhí)行插入之前需要加鎖,防止多線程的問(wèn)題。
SQLiteStatement rawStmt = (SQLiteStatement) stmt.getRawStatement();
for (T entity : entities) {
bindValues(rawStmt, entity);
if (setPrimaryKey) {
//執(zhí)行Sql語(yǔ)句 并返回對(duì)象的rowId
long rowId = rawStmt.executeInsert();
updateKeyAfterInsertAndAttach(entity, rowId, false);
} else {
rawStmt.execute();
}
}</pre>
可以看到,多數(shù)據(jù)插入也只是遍歷插入而已。
插入后依然是更新ID,然后就是內(nèi)存緩存,我們來(lái)看看下面這個(gè)方法:
protected final void attachEntity(K key, T entity, boolean lock) {
attachEntity(entity);
if (identityScope != null && key != null) {
if (lock) {
identityScope.put(key, entity);
} else {
identityScope.putNoLock(key, entity);
}
}
}
可以看到identityScope 就是用來(lái)維護(hù)內(nèi)存緩存的鍵值對(duì),通過(guò)判斷是否加鎖執(zhí)行相應(yīng)的put操作。
說(shuō)到這里,GreenDao是怎么優(yōu)化和做緩存的大家應(yīng)該都大致了解了吧:
1、通過(guò)Statement的復(fù)用,達(dá)到優(yōu)化的效果,這是所有數(shù)據(jù)庫(kù)都通用的。
2、通過(guò)Key映射保存到內(nèi)存中,保存的值當(dāng)前是軟引用拉,要不很容易爆表。
其他操作類型大家可以花店心思去學(xué)一下。
還有就是GreenDao除了用弱引用外,在Key為L(zhǎng)ong時(shí)還特別做了Map的優(yōu)化,我們將單獨(dú)抽出來(lái)說(shuō)。