DaoMaster、具体的Dao 和 DaoSession对象为greedao生成的代码 从平时的使用能够看出他们的做用java
GreenDao的总入口,负责整个库的运行,实现了SqliteOpenHelper
会话层,操做Dao的具体对象,包括DAO对象的注册
实体类,和表内容一一对应
生成的DAO对象,进行具体的数据库操做
这几个类的关系以下UML图: sql
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经过AbstractDAO类实现数据库的增删改查逻辑,此处分析几个经常使用的方法性能
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);
}
复制代码
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 = '?'
复制代码
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;
}
复制代码
在上面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给咱们提供了异步的方法。 大体用法为:
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对象的数据库操做方法。
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使用的时候,会自动生成DaoMaster,DAO对象等等java文件。大体翻阅了DaoGenerator这个Module里面的代码。发现有模板引擎的库依赖:
compile 'org.freemarker:freemarker:2.3.23'
复制代码
而且发现了freemarker的模板文件 在DaoGenerator/src-template里面,有不少的.ftl文件。GreenDAO的文件就是经过freemarker模板生成的Java文件。具体的原理下次分析后会单独再写一篇博客。
经过对GreenDAO的源码分析,能够发现GreenDAO号称本身是性能最好的ORM库也是有缘由的,GreenDAO的特色总结为如下: