GreenDao3.0 原始碼分析-Dao層
GreenDao3.0系列文章:
Dao 是GreenDao進行資料查詢的一層,起到非常重要的作用,今晚我們就來聊聊GreenDao是如何做增刪改查的吧。
Order實體
我們從稍微複雜的Order進行分析,去除自動生成的程式碼,源實體是:如上圖,Order和Customer物件是一對一的關係,下面我們來看看生成的OrderDao:@Entity(active = true, nameInDb = "ORDERS") public class Order { @Id private Long id; private java.util.Date date; private long customerId; @ToOne(joinProperty = "customerId") private Customer customer; }
OrderDao
以下幾點是我歸納的要點1、所有的實體DAO物件都繼承自 AbstractDao<Order, Long>。
2、TABLENAME常量定義了資料庫表名,預設為實體類名大寫,nameInDb 可以自定義表名。
3、每個實體Dao都有一個Properties來管理物件實體屬性對錶各列的對映關係,對像為Property。
4、提供建立表和刪除表的實現。
5、提供Statement繫結對應例項的方法。
6、讀取Cursor轉化成物件。
7、獲得主鍵。
以上是最基礎的操作,是必須的,還有因為一些特殊的:
8、當Java實體需要轉成其他型別,比如String儲存時,需要提供一個轉化器PropertyConverter
9、為了查詢告訴,把實體Id設定成RowId
10、還有就是一對多關係,建立的一些關聯性程式碼。
上面歸納的就是實體Dao提供的功能,下面我們逐步對程式碼進行解析:
1、2、3我就不說了,非常簡單,就是通過 Property屬性物件來進行管理,每一個Property就是物件資料庫的一列。
3、4是資料庫的基本操作,通過Database資料庫物件執行Sql語句建立表和刪除表,每次建立刪除是通過DaoMaster進行操作的,兩個方法都是靜態方法。
我們來簡單說下5:
SQLiteStatement是我們定義好的一些增刪改查語句的宣告,通過以?來做佔位符,達到提供效能的目的,這裡就是把Order例項的資料資訊,按照序號,進行繫結到SQLiteStatement中,以供資料庫做查詢等操作,從上面原始碼我們還能看到,GreenDao的轉化器,其實就是按照一定的規則,生成對映的Date2LongConver dateConverter = new Date2LongConver();物件,然後執行方法,達到轉換的母目的。 下面我們看看讀物件的操作: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()); }
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; }
讀的操作也是非常簡單,通過判空後進行賦值,相應的需要轉化的物件也會轉化。
7、8、9各位看官自己檢視,比較簡單getKey(Order entity)獲取主鍵,updateKeyAfterInsert(Order entity, long rowId)是插入成功後更換Key,hasKey(Order entity)判斷是否有主鍵。
下面我們說說,關於GreenDao是如何實現一對多的問題:
我們再來引入一個類Customer,Customer類和Order是一對多的關係
@ToMany(joinProperties = {
@JoinProperty(name = "id", referencedName = "customerId")
})
@OrderBy("date ASC")
private List<Order> orders;
GreenDao處理一對多的關係,是通過取得OrderDao的引用來進行查詢:
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,我們再看看_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();
}
思路已經很清晰了,就通過獲取多物件的Dao引用,進行查詢操作,因為這裡還新增時間的排序,所以新增增加了時間排序,一對一的關係也大致如此。
Dao已經說完了,接下來我們來進行AbstractDao的解析
AbstractDao
AbstractDao封裝了和資料庫進行的增刪改查功能。
大致功能如下圖:
因為操作類似,我們這裡只分析插入部分,其他部分可通過自己閱讀完成理解:
可以看到上面的思維導圖。子樹是面對使用者的API,最終單個實體插入會執行到insertInsideTx,而多資料實體會執行到executeInsertInTx。
這裡我來理一下思路,GreenDao不管是做查詢還是其他操作都是使用Statement來進行優化效能的,而且Statement是可以重用的,所以GreenDao有自己的Statement管理類,就是TableStatements,我們來看看TableStatements對插入宣告的建立:
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維護著一個insertStatement物件,如果不為null就直接返回,為null就拼接建立,以達到複用優化效能的作用,這是資料庫常見的操作,SqlUtils工具類是Sql語句拼接的工具,大家有興趣自己看一下。
我們來先聊聊單個實體做插入的時候,從面向使用者的API獲取到想用的插入,或者插入或替換的Statement後,插入操作會執行
executeInsert(T entity, DatabaseStatement stmt, boolean setKeyAndAttach)方法:
private long executeInsert(T entity, DatabaseStatement stmt, boolean setKeyAndAttach) {
long rowId;
//先判斷當前執行緒是否連線了資料庫
if (db.isDbLockedByCurrentThread()) {
//返回true 直接插入資料
rowId = insertInsideTx(entity, stmt);
} else {
//在鎖定stmt之前通過開啟transation請求連線
db.beginTransaction();
try {
rowId = insertInsideTx(entity, stmt);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
if (setKeyAndAttach) {
updateKeyAfterInsertAndAttach(entity, rowId, true);
}
return rowId;
}
到這裡 插入步驟如下:
1、通過判斷當前執行緒是否和資料庫關聯來決定是否需要開啟事務。
2、最後都執行insertInsideTx,裡面的操作很簡單就是呼叫之前子類的bindValue方法進行繫結值後執行Sql操作。
3、插入後的善後處理,這裡就是更新實體的ID為RowId和做記憶體快取,還有一些特殊操作實體繫結DaoSeesion,使用active = true會用到。
我再來看看executeInsertInTx,獲取到Statement類似,因為是多資料插入,強制使用事務:
這裡我們再引入一個概念,就是IdentityScope<K, T>是GreenDao用來做記憶體快取的,可以看成是一個Map,如果是Long,GreenDAO做了對應的優化,因為多資料插入是比較耗時的,所以,我們執行插入之前需要加鎖,防止多執行緒的問題。
SQLiteStatement rawStmt = (SQLiteStatement) stmt.getRawStatement();
for (T entity : entities) {
bindValues(rawStmt, entity);
if (setPrimaryKey) {
//執行Sql語句 並返回物件的rowId
long rowId = rawStmt.executeInsert();
updateKeyAfterInsertAndAttach(entity, rowId, false);
} else {
rawStmt.execute();
}
}
可以看到,多資料插入也只是遍歷插入而已。
插入後依然是更新ID,然後就是記憶體快取,我們來看看下面這個方法:
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 就是用來維護記憶體快取的鍵值對,通過判斷是否加鎖執行相應的put操作。
說到這裡,GreenDao是怎麼優化和做快取的大家應該都大致瞭解了吧:
1、通過Statement的複用,達到優化的效果,這是所有資料庫都通用的。
2、通過Key對映儲存到記憶體中,儲存的值當前是軟引用拉,要不很容易爆表。
其他操作型別大家可以花店心思去學一下。
還有就是GreenDao除了用弱引用外,在Key為Long時還特別做了Map的優化,我們將單獨抽出來說。