簡述
DaoMaster、具體的Dao 和 DaoSession對象爲greedao生成的代碼
從平時的使用可以看出他們的作用
- DaoMaster
GreenDao的總入口,負責整個庫的運行,實現了SqliteOpenHelper
- DaoSession
會話層,操作Dao的具體對象,包括DAO對象的註冊
- xxEntity
實體類,和表內容一一對應
- xxDao
生成的DAO對象,進行具體的數據庫操作
這幾個類的關係如下UML圖:
GreenDAO.png
Dao對象需要依賴DaoConfig對象
public DaoConfig(Database db, Class<? extends AbstractDao<?, ?>> daoClass)
DaoConfig對象需要傳入具體的DaoClass類型
reflectProperties(Class<? extends AbstractDao<?, ?>> daoClass))
獲取DAO裏面的Properties 所有static或者public字段
pkProperty = pkColumns.length == 1 ? lastPkProperty : null;
statements = new TableStatements(db, tablename, allColumns, pkColumns);
if (pkProperty != null) {
Class<?> type = pkProperty.type;
keyIsNumeric = type.equals(long.class) || type.equals(Long.class) || type.equals(int.class)
|| type.equals(Integer.class) || type.equals(short.class) || type.equals(Short.class)
|| type.equals(byte.class) || type.equals(Byte.class);
} else {
keyIsNumeric = false;
}
這裏會獲取所有的數據庫字段,順便判斷表主鍵是否是數字類型
數據庫加密
GreenDAO創建會話的時候我們一般會調用如下代碼獲取DaoSession對象:
DevOpenHelper helper = new DevOpenHelper(this, ENCRYPTED ? "notes-db-encrypted" : "notes-db");
Database db = ENCRYPTED ? helper.getEncryptedWritableDb("super-secret") : helper.getWritableDb();
daoSession = new DaoMaster(db).newSession();
在AbstractDAO類中,有一個db字段,最終的數據庫操作以及事務的開啓都會通過這個對象開啓。GreenDAO中存在Database和DatabaseStatement2個接口
這2個接口分別存在2個子類StandardDatabase、EncryptedDatabase 和 StandardDatabaseStatement 、EncryptedDatabaseStatement,這幾個類其實都是一個代理模式
public class StandardDatabaseStatement implements DatabaseStatement {
private final SQLiteStatement delegate;
public StandardDatabaseStatement(SQLiteStatement delegate) {
this.delegate = delegate;
}
@Override
public void execute() {
delegate.execute();
}
}
public class EncryptedDatabase implements Database {
private final SQLiteDatabase delegate;
public EncryptedDatabase(SQLiteDatabase delegate) {
this.delegate = delegate;
}
@Override
public Cursor rawQuery(String sql, String[] selectionArgs) {
return delegate.rawQuery(sql, selectionArgs);
}
}
這裏會發現GreenDAO調用了一個三方庫叫做sqlcipher.,提供了Sqlite的數據庫加密功能。所以GreenDAO在創建一次會話的時候可以指定數據庫是否加密。如果沒加密,會使用Android的Sqlite API去操作數據庫,如果加密,則使用sqlcipher提供發API去操作數據庫。
GreenDAO的增刪改查
GreenDAO通過AbstractDAO類實現數據庫的增刪改查邏輯,此處分析幾個常用的方法
- insert 插入數據
public long insert(T entity) {
return executeInsert(entity, statements.getInsertStatement(), true);
}
內部邏輯僞代碼:
if (db.isDbLockedByCurrentThread()) {
// 數據庫連接被其他佔用
rowId = insertInsideTx(entity, stmt);
} else {
// Do TX to acquire a connection before locking the stmt to avoid deadlocks (開啓事務防止死鎖)
db.beginTransaction();
try {
rowId = insertInsideTx(entity, stmt);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
if (setKeyAndAttach) {
updateKeyAfterInsertAndAttach(entity, rowId, true);
}
- update 更新數據
public void update(T entity)
update的源碼大概爲
assertSinglePk(); //判斷是否只有一個主鍵
DatabaseStatement stmt = statements.getUpdateStatement();
if (db.isDbLockedByCurrentThread()) {
synchronized (stmt) {
if (isStandardSQLite) {
updateInsideSynchronized(entity, (SQLiteStatement) stmt.getRawStatement(), true);
} else {
updateInsideSynchronized(entity, stmt, true);
}
}
} else {
// Do TX to acquire a connection before locking the stmt to avoid deadlocks
db.beginTransaction();
try {
synchronized (stmt) {
updateInsideSynchronized(entity, stmt, true);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
關注updateInsideSynchronized方法
bindValues(stmt, entity);
int index = config.allColumns.length + 1;
K key = getKey(entity);
if (key instanceof Long) {
stmt.bindLong(index, (Long) key);
} else if (key == null) {
throw new DaoException("Cannot update entity without key - was it inserted before?");
} else {
stmt.bindString(index, key.toString());
}
stmt.execute();
attachEntity(key, entity, lock);
和insert方法類似,添加了主鍵判斷必須存在key的邏輯
其中添加了
int index = config.allColumns.length + 1;
這個index bind的字段是update的條件語句,和id進行綁定
換成sql語句就是
where id = '?'
- select 查詢操作
查詢的代碼和insert、update大致流程一致
會通過QueryBuilder構造查詢條件,在QueryBuilder中list方法獲取數據
public List<T> list() {
return build().list();
}
public List<T> list() {
checkThread();
Cursor cursor = dao.getDatabase().rawQuery(sql, parameters);
return daoAccess.loadAllAndCloseCursor(cursor);
}
會走到AbstractDao類的loadAllFromCursor方法
if (cursor.moveToFirst()) {
if (identityScope != null) {
identityScope.lock();
identityScope.reserveRoom(count);
}
try {
if (!useFastCursor && window != null && identityScope != null) {
loadAllUnlockOnWindowBounds(cursor, window, list);
} else {
do {
list.add(loadCurrent(cursor, 0, false));
} while (cursor.moveToNext());
}
} finally {
if (identityScope != null) {
identityScope.unlock();
}
}
}
會走到loadCurrent方法
final protected T loadCurrent(Cursor cursor, int offset, boolean lock)
此處可以關於IdentityScope的代碼
T entity = lock ? identityScopeLong.get2(key) : identityScopeLong.get2NoLock(key);
if (entity != null) {
return entity;
} else {
entity = readEntity(cursor, offset);
attachEntity(entity);
if (lock) {
identityScopeLong.put2(key, entity);
} else {
identityScopeLong.put2NoLock(key, entity);
}
return entity;
}
- delete 刪除的邏輯和增改查大致一樣
GreenDAO緩存
在上面query的源碼中可以發現GreenDAO對於數據做了一次內存的緩存,每次寫數據庫的時候會從IdentityScope的map 中put一次數據。每次讀數據庫的時候會從IdentityScope的map中get一次數據。如果map中緩存拿到了,就使用緩存作爲查詢的結果。
在查詢和更新數據的時候會執行
attachEntity(K key, T entity, boolean lock)
方法,具體邏輯如下
if (identityScope != null && key != null) {
if (lock) {
identityScope.put(key, entity);
} else {
identityScope.putNoLock(key, entity);
}
}
關於數據庫緩存的特性,我們需要視業務情況而定,在網上還是能搜到GreenDAO查詢數據沒有拿到最新結果的bug, 如果出現這個bug且需要拿到最新的數據庫信息,可以使用DaoSession的clear方法刪除緩存,源碼如下
//DaoSession
public void clear() {
noteDaoConfig.clearIdentityScope();
}
//DaoConfig
public void clearIdentityScope() {
IdentityScope<?, ?> identityScope = this.identityScope;
if(identityScope != null) {
identityScope.clear();
}
}
GreenDAO的異步操作
有些時候,我們希望異步的操作數據庫,GreenDAO給我們提供了異步的方法。
大致用法爲:
AsyncSession asyncSession = daoSession.startAsyncSession();
asyncSession.setListener(new AsyncOperationListener() {
@Override
public void onAsyncOperationCompleted(AsyncOperation operation) {
AsyncOperation.OperationType type = operation.getType();
Log.e(TAG, type.name());
}
});
asyncSession.insert(note);
我們獲取一個AsyncSession對象進行異步的數據庫操作,並且可以設置AsyncOperation對異步操作進行監聽。但是爲什麼所有的異步操作是同一個Listener回調呢?我們可以在源碼中找到答案。
查看AsyncSession的insert方法
public AsyncOperation insert(Object entity) {
return insert(entity, 0);
}
最後可以走到enqueEntityOperation方法
AbstractDao<?, ?> dao = daoSession.getDao(entityClass);
AsyncOperation operation = new AsyncOperation(type, dao, null, param, flags | sessionFlags);
executor.enqueue(operation);
return operation;
可以發現有一個異步操作的Executor,AsyncOperationExecutor
查看enqueue方法
operation.sequenceNumber = ++lastSequenceNumber;
queue.add(operation);
countOperationsEnqueued++;
if (!executorRunning) {
executorRunning = true;
executorService.execute(this);
}
可以看到它把異步加入到一個隊列裏面排隊等待。在線程池中執行這些操作。
查看run方法
//線程池run方法代碼
while (true) {
AsyncOperation operation = queue.poll(1, TimeUnit.SECONDS);
if (operation == null) {
synchronized (this) {
// Check again, this time in synchronized to be in sync with enqueue(AsyncOperation)
operation = queue.poll();
if (operation == null) {
// set flag while still inside synchronized
executorRunning = false;
return;
}
}
}
if (operation.isMergeTx()) {
// Wait some ms for another operation to merge because a TX is expensive
AsyncOperation operation2 = queue.poll(waitForMergeMillis, TimeUnit.MILLISECONDS);
if (operation2 != null) {
if (operation.isMergeableWith(operation2)) {
mergeTxAndExecute(operation, operation2);
} else {
// Cannot merge, execute both
executeOperationAndPostCompleted(operation);
executeOperationAndPostCompleted(operation2);
}
continue;
}
}
executeOperationAndPostCompleted(operation);
}
這段代碼我們可以看到,每次從隊列中拿一個異步操作對象執行邏輯。這也解釋了爲什麼外層只需要set一個Listener。GreenDAO的異步操作是所有的數據庫操作在一個子線程中進行同步操作。
最終代碼會走到executeOperation方法
switch (operation.type) {
case Delete:
operation.dao.delete(operation.parameter);
break;
default:
break;
}
這裏會執行具體的DAO對象的數據庫操作方法。
和ReactiveX的結合
GreenDAO提供了API與rxjava結合使用,代碼如下:
RxDao<xxEntity, Long> xxDao = daoSession.getXXDao().rx();
xxDao.insert(xxEntity)
.observerOn(AndroidSchedules.mainThread())
.subscribe(new Action1<xxEntity>() {
@Override
public void call(xxEntity entity) {
// insert success
}
})
我們來簡單分析下源碼看看RxJava是如何和GreenDAO結合的
查看AbstractDAO的rx()
public RxDao<T, K> rx() {
if (rxDao == null) {
rxDao = new RxDao<>(this, Schedulers.io());
}
return rxDao;
}
查看RxDAO的構造方法
public RxDao(AbstractDao<T, K> dao, Scheduler scheduler) {
super(scheduler);
this.dao = dao;
}
包含了Dao對象和線程調度Scheduler對象,這裏是io線程,即在異步線程執行
查看insert方法
public Observable<T> insert(final T entity) {
return wrap(new Callable<T>() {
@Override
public T call() throws Exception {
dao.insert(entity);
return entity;
}
});
}
查看wrap方法, wrap方法裏面也調用了一個重載的wrap方法。
參數爲一個Callable對象,裏面執行了數據庫插入的邏輯,返回了實體類對象。
protected <R> Observable<R> wrap(Callable<R> callable) {
return wrap(RxUtils.fromCallable(callable));
}
if (scheduler != null) {
return observable.subscribeOn(scheduler);
} else {
return observable;
}
這裏的第二個wrap方法的參數observable是通過RxUtils.fromCallable獲得的,查看這個方法的源碼
static <T> Observable<T> fromCallable(final Callable<T> callable) {
return Observable.defer(new Func0<Observable<T>>() {
@Override
public Observable<T> call() {
T result;
try {
result = callable.call();
} catch (Exception e) {
return Observable.error(e);
}
return Observable.just(result);
}
});
}
這裏使用了defer操作符創建一個Observable對象。延遲創建,確保Observable被訂閱後才執行。
以上就是rxjava和GreenDAO的結合使用的原理。和rx'java結合使用,會使GreenDAO尤其是異步操作寫起來更加的優雅。
GreenDAO代碼生成
我們在GreenDAO使用的時候,會自動生成DaoMaster,DAO對象等等java文件。大致翻閱了DaoGenerator這個Module裏面的代碼。發現有模板引擎的庫依賴:
compile 'org.freemarker:freemarker:2.3.23'
並且發現了freemarker的模板文件
在DaoGenerator/src-template裏面,有很多的.ftl文件。GreenDAO的文件就是通過freemarker模板生成的Java文件。具體的原理下次分析後會單獨再寫一篇博客。
總結
通過對GreenDAO的源碼分析,可以發現GreenDAO號稱自己是性能最好的ORM庫也是有原因的,GreenDAO的特點總結爲以下:
- 使用了靜態代碼生成,不通過反射運行時生成代理類,提升了性能
- 使用了SQLiteStatement
- 同時提供了同步和異步的數據庫操作方式
- 數據庫提供內存緩存,更高效的查詢
- 基於sqlcipher提供加密數據庫
- 提供RxJava的API,異步操作更高效
作者:preCh
鏈接:https://www.jianshu.com/p/d049035cf177
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。