众所周知,GreenDao操作数据库的性能比其他ORM注解框架甚至是原生API都要快许多,现在来看看GreenDao比其它框架快的原因。

  1. 避免使用注解和反射拼装sql语句
  2. 一些方法基于SQLiteStatement实现的(比原生的execSQL方法快一些)
  3. 最终执行时开启了事务
  4. 支持异步查询和回调
  5. 查询缓存机制,使用了弱引用WeakReference,第一次查询时将数据加入SparseArray>的集合中
    所以要想自己来优化数据库的性能也可以从这些方面来考虑。

GreenDao了解

GreenDao的核心类如下:

核心类结构图

DaoMaster 保存了SqliteDatabase对象,提供了一些创建和删除表的静态方法(OpenHelper和DevOpenHelper)

DaoSession 会话层,操作具体对象

xxxDao 实际生成的数据库类

xxxEntity 持久的实体对象,对应着具体的数据库

其基本的数据库操作如下:

1
2
3
4
5
6
7
8
DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db", null);
db = helper.getWritableDatabase();
daoMaster = new DaoMaster(db);
daoSession = daoMaster.newSession();
noteDao = daoSession.getNoteDao();
Note note = new Note(null, noteText, comment, new Date());
noteDao.insert(note);
noteDao.deleteByKey(id);

1.使用SQLiteStatement

优化前:

1
2
3
4
5
6
7
8
9
10
if (database.isOpen()) {
database.beginTransaction(); // 开启事务
try {
database.execSQL(sql);
database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
database.close();
}

优化后:

1
2
3
4
5
6
7
8
9
10
11
12
13
SQLiteStatement statement = database.compileStatement(sql);
if (database.isOpen()) {
database.beginTransaction();
try {
//index 为1开始索引,value为入库的值
//bingXXX为插入XXX类型
statement.bindString(index, value);
database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
database.close();
}

插入数据有3种方式:

  1. 直接拼装sql,执行execSQL方法,但是它存在SQL注入危险且效率也稍差一些
  2. 借用ContentValues可解决SQL注入漏洞问题
  3. 使用database.compileStatement
    3种方式从本质上来说都是创建了一个SQLiteStatement对象,然后调用其executeUpdateDelete插入方法,最后调用native方法完成操作。
    执行SQL产生SQLiteStatement对象之后,会通过session调用连接池中某个connection中的execute方法。在connection中会构建一个PreparedStatement对象且其含有指向native的指针。

2.使用事务Transaction

上面的代码已使用过事务对插入操作进行过优化。
使用事务可以保证数据的统一性和完整性,同时也可以提高效率。事务是可以把启动事务过程中的所有操作视为事务的过程。等到所有过程执行完毕后,我们可以根据操作是否成功来决定事务是否进行提交或者回滚。提交事务后会一次性把所有数据提交到数据库,如果回滚了事务就会放弃这次的操作,而对原来表的数据不进行更改。
使用SQLiteDatabase的beginTransaction()方法可以开启一个事务,程序执行到endTransaction() 方法时会检查事务的标志是否为成功,如果程序执行到endTransaction()之前调用了setTransactionSuccessful() 方法设置事务的标志为成功则提交事务,如果没有调用setTransactionSuccessful() 方法则回滚事务。
事务有4个属性:

  • 原子性:确保工作单位内的所有操作都成功完成
  • 一致性:确保数据库在成功提交的事务上正确地改变状态
  • 隔离性:使事务操作相互独立和透明
  • 持久性:确保已提交事务的结果或效果在系统发生故障的情况下仍然存在

3.使用List缓存数据或者按需缓存(操作较大的结果集时采用懒加载的方式)

TODO:
分析GreenDao的QueryBuilder相关API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//断言主键唯一性
assertSinglePk();
if (key == null) {
return null;
}
//存在数据作用域的缓存
if (identityScope != null) {
//如果从缓存中获取到数值 直接返回
T entity = identityScope.get(key);
if (entity != null) {
return entity;
}
}
//缓存中不存在数据 创建查询的sql语句
String sql = statements.getSelectByKey();
String[] keyArray = new String[] { key.toString() };
Cursor cursor = db.rawQuery(sql, keyArray);
return loadUniqueAndCloseCursor(cursor);

实现的方法例如,第一次查询时利用LRUCache将数据添加到一个LinkedHashMap中,如果对某个数据表进行过增删改操作则更新这个列表或者是删除列表的相关记录,查询是先从缓存列表中获取,没有时再创建SQL语句并执行查询操作。


总结

GreenDao是一个相对简洁的第三方库,其本质上提高查询效率的办法是直接使用SQLiteStatement和事务,由于其对事务的良好管理和对Android官方提供的API的良好分装,以及使用java程序来生成数据库相关代码,为开发带来很大的便利。而其它框架简化代码的方式是通过注解和反射,对执行效率有着很大的损伤。另外,良好的封装和内存管理也意味着数据库操作引起内存泄漏的可能性降低了。

参考博客