【遷移博客】GreenDao源碼

GreenDao源碼

簡述

DaoMaster、具體的Dao 和 DaoSession對象爲greedao生成的代碼 從平時的使用能夠看出他們的做用java

  • DaoMaster GreenDao的總入口,負責整個庫的運行,實現了SqliteOpenHelper
  • DaoSession 會話層,操做Dao的具體對象,包括DAO對象的註冊
  • xxEntity 實體類,和表內容一一對應
  • xxDao 生成的DAO對象,進行具體的數據庫操做

這幾個類的關係以下UML圖: sql

GreenDAO.png

Dao對象須要依賴DaoConfig對象數據庫

public DaoConfig(Database db, Class<? extends AbstractDao<?, ?>> daoClass) 複製代碼

DaoConfig對象須要傳入具體的DaoClass類型緩存

reflectProperties(Class<? extends AbstractDao<?, ?>> daoClass))
複製代碼

獲取DAO裏面的Properties 全部static或者public字段session

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對象:async

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,這幾個類其實都是一個代理模式ide

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,異步操做更高效
相關文章
相關標籤/搜索