GreenDao3.0系列文章:
GreenDao3.0源碼分析-Helper
GreenDao3.0 源碼分析-DaoMaster和DaoSeesion
GreenDao3.0 源碼分析-Dao層
Dao 是GreenDao進行資料查詢的一層,起到非常重要的作用,今晚我們就來聊聊GreenDao是如何做增删改查的吧。
Order實體
我們從稍微複雜的Order進行分析,去除自動生成的代碼,源實體是:
@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;
}
如上圖,Order和Customer對象是一對一的關系,下面我們來看看生成的OrderDao:
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:
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());
}
SQLiteStatement是我們定義好的一些增删改查語句的聲明,通過以?來做占位符,達到提供性能的目的,這裡就是把Order執行個體的資料資訊,按照序号,進行綁定到SQLiteStatement中,以供資料庫做查詢等操作,從上面源碼我們還能看到,GreenDao的轉化器,其實就是按照一定的規則,生成對映的Date2LongConver dateConverter = new Date2LongConver();對象,然後執行方法,達到轉換的母目的。 下面我們看看讀對象的操作:
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封裝了和資料庫進行的增删改查功能。
大緻功能如下圖:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX9kFRORTSE90MBRVT3V1MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2LcRHelR3LcJzLctmch1mclRXY39jN5UDO0UjMwEzMxUDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
AbstractDao提供了插入、更新、删除、儲存,查詢等功能,額為功能還包括對Rx1.0的适配,統計表的行數等。
因為操作類似,我們這裡隻分析插入部分,其他部分可通過自己閱讀完成了解:
可以看到上面的思維導圖。子樹是面對使用者的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的優化,我們将單獨抽出來說。