目前大部分开源数据库都是基于SQLite发展而来,如SQLBrite、OrmLite、DBFlow、GreenDao等等,还有一个是Realm框架不是基于 SQLite 创建,它建立了自己独特的数据库存储引擎。那这么多框架到底有什么区别,那种更适合自己的产品呢?
我们先看基于SQLite发展而来的框架,这方面主要分成两条发展路线,一条是Rx或者叫做对象映射(OM)路线,一条是对象关系映射(ORM)路线。
Rx/对象映射路线
代表数据库有sqlbrite、sqldelight,这两个都是Square出品。
SqlBrite和SqlDelight都是对象映射(OM,Object Mappers)而不是对象关系映射(ORM,Object/Relational Mappers)。
ORM 其实并不是一个优秀的框架。很多平台的 ORM 实现都有性能和内存的问题。我们也不会编写ORM。 – JakeWharton
上面这句话很好的说明了这两个框架的出发点,知道这些我们就容易理解了。
SqlBrite
SqlBrite是对 Android 系统的 SQLiteOpenHelper 的封装,对SQL操作引入了响应式语义 (Rx)(用来在 RxJava 中使用)
基本用法
- 创建一个SqlBrite对象,该对象是该库的入口:
SqlBrite sqlBrite = SqlBrite.create();
- 提供一个 SQLiteOpenHelper实例和一个Scheduler实例来创建一个 BriteDatabase 对象:
BriteDatabase db = sqlBrite.wrapDatabaseHelper(openHelper, Schedulers.io());
,Scheduler 是指定执行查询的操作的线程,由于查询数据库是不建议在 UI 线程中执行的,所以一般指定 Schedulers.io() 。 - BriteDatabase.createQuery方法和SQLiteDatabase.rawQuery方法相比,多了一个table(s)表参数,用于监听数据变更。当我们订阅subscribe返回的Observable的时候,立刻执行需要的查询语句。
Observable users = db.createQuery("users", "SELECT * FROM users");
users.subscribe(new Action1() {
@Override public void call(Query query) {
Cursor cursor = query.run();
// TODO parse data...
}
});
优点
在保证性能和复杂扩展性的同时,利用Rxjava操作Sql在易用性上有部分提高。
缺点
还需要编写Sql语句,复杂性还是较高。
SqlDelight
SqlDelight通过从 SQL 语句来生成 JAVA 模型代码。这样的好处是,所有 SQL 语句都位于同一个位置,通过查看 SQL 语句可以清楚的了解需要实现的功能和数据库的结构,也便于管理以及java类访问。
基本用法
需要把 SQL 语句放到对应的 .sq 文件中,默认目录为和 main 目录下的 java 代码同级,例如
src/main/sqldelight/com/example/HockeyPlayer.sq ,其中 com/example/ 为对应 java 对象的包名字。 在该 .sq 文件中一般第一个语句是创建表的语句:
CREATE TABLE hockey_player (
_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
number INTEGER NOT NULL,
name TEXT NOT NULL
);
-- 其他的语句通过标识符来引用。在生成的 Java 对象中会包含
-- 一个该标识符的常亮引用这个语句。
select_by_name:
SELECT *
FROM hockey_player
WHERE name = ?;
上面的 SQL 语句会生成一个 HockeyPlayerModel Java 接口。该接口内有两个嵌套类分别把 Cursor 映射为 Java 对象以及把 Java 对象转换为 ContentValues 好插入数据库
优点
- 所有的SQL statement都存在.sq文件中,便于管理
- 可以自由的使用普通SQLite的同时帮助你处理了程式化的代码
缺点
需要编写原生sql
总结
SqlBrite方便在 RxJava 中使用 Sql 操作,并且额外添加了对数据库表数据更新通知的机制,当你对数据表进行操作的时候,其他订阅者可以在数据发生变化的时候收到通知。然后可以用 RxJava 的方式来操作数据。只是一个 SQLiteOpenHelper 的轻量级封装,并不关心你的对象是如何实现的,也不关心你的数据库。SqlBrite也不支持对象映射和类型安全的查询,SqlBrite 不是一个 ORM 框架,也不是一个类型安全的查询框架。不会提供类似Gson中对象序列化的功能,也不会提供数据库迁移的功能。
SqlDelight 的做法是从 SQL 语句来生成 JAVA 模型代码。 这样的好处是,所有 SQL 语句都位于同一个位置。SqlDelight 添加了对 SQL 语句的编译时验证、表名字和列名字的代码自动完成功能。让编写 SQL 语句更加快捷。在编译的时候,根据 SQL 语句生成 Java 模型接口和 builder 来把数据行和 Java 对象实现转换。SqlDelight 不会做很重的功能(比如数据懒加载、缓存 、级联删除 等 ORM 框架内常见的功能)。
对象关系映射(ORM)路线
这部分框架在易用性上和性能上都做了很多工作,易用性上基本达到极致,在性能上接近原生Sql,这里举几个有代表性的项目。
OrmLite
OrmLite - Lightweight Object Relational Mapping (ORM) Java Package
基本原理
- 使用注解方式标示字段,如数据库、表等
- 运行时使用反射获取相应字段拼接sql去执行
优点
在易用性上相比原生sql有较大提高
缺点
性能上有损失
greenDAO
greenDAO is an open source Android ORM making development for SQLite databases fun again.
基本原理、特点
- 使用注解方式标示字段
- 在编译期生成本地sql
- greenDAO 支持 protocol buffer(protobuf) 协议数据的直接存储,如果你通过 protobuf 协议与服务器交互,将不需要任何的映射。
- 数据加密
解析
DaoMaster保存了sqlitedatebase对象以及操作DAO classes。其提供了一些创建和删除table的静态方法,其内部类OpenHelper和DevOpenHelper实现了SQLiteOpenHelper并创建数据库的框架。
DaoMaster除了具有创建表和删除表的两个功能外,还有两个内部类,分别为OpenHelper和DevOpenHelper,而DevOpenHelper继承自OpenHelper,而OpenHelper继承自SQLiteOpenHelper,而重写的onCreate()方法中调用了createAllTables(db,false);方法来创建数据表,而createAllTables()方法中是通过调用UserDao静态方法来创建表的UserDao.createTable(db, ifNotExists);
/** Creates the underlying database table. */
public static void createTable(SQLiteDatabase db, boolean ifNotExists) {
String constraint = ifNotExists? "IF NOT EXISTS ": "";
db.execSQL("CREATE TABLE " + constraint + "\\"NOTE\\" (" + //
"\\"_id\\" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id
"\\"TEXT\\" TEXT NOT NULL ," + // 1: text
"\\"COMMENT\\" TEXT," + // 2: comment
"\\"DATE\\" INTEGER);"); // 3: date
}
/** Drops the underlying database table. */
public static void dropTable(SQLiteDatabase db, boolean ifExists) {
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\\"NOTE\\"";
db.execSQL(sql);
}
greenDAO的增删改查方法有一些是在Android原生的操作方法上进行了封装,对于链式查询的最终执行也是调用了Android原生的查询操作。
public List<T> list() {
checkThread();
Cursor cursor = dao.getDatabase().rawQuery(sql, parameters);
return daoAccess.loadAllAndCloseCursor(cursor);
}
同时还有一些方法是基于SQLiteStatement实现的,SQLiteStatement相比原生的execSQL方法还要快一些,并且最终执行时也开启了事务,性能又提升了很多。下面是插入数据的最终实现方法:
private long executeInsert(T entity, SQLiteStatement stmt) {
long rowId;
if (db.isDbLockedByCurrentThread()) {
synchronized (stmt) {
bindValues(stmt, entity);
rowId = stmt.executeInsert();
}
} else {
// Do TX to acquire a connection before locking the stmt to avoid deadlocks
db.beginTransaction();
try {
synchronized (stmt) {
bindValues(stmt, entity);
rowId = stmt.executeInsert();
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
updateKeyAfterInsertAndAttach(entity, rowId, true);
return rowId;
}
可以看到先执行bindValues方法,该方法是一个抽象方法,需要业务方在DAO文件中实现,跟踪至NoteDao文件查看该方法代码如下:
@Override
protected void bindValues(SQLiteStatement stmt, Note entity) {
stmt.clearBindings();
Long id = entity.getId();
if (id != null) {
stmt.bindLong(1, id); // 1为索引值,id为入库的值
}
stmt.bindString(2, entity.getText());
String comment = entity.getComment();
if (comment != null) {
stmt.bindString(3, comment);
}
java.util.Date date = entity.getDate();
if (date != null) {
stmt.bindLong(4, date.getTime());
}
}
这样就将SQLiteStatement需要的数据都进行了封装,然后执行stmt.executeInsert()方法即可完成数据库的插入操作。整个数据插入流程,greenDAO借助SQLiteStatement完成了数据的插入,避免了其他框架利用反射拼装sql语句而造成的执行效率低下的问题。
其他优化:
- 避免使用注解和反射拼装sql语句
- 最终执行时开启了事务
- 支持异步查询和回调
- 查询缓存机制,使用了弱引用WeakReference,第一次查询时将数据加入SparseArray>的集合中
优点
在易用性和性能上做到了很好的平衡
缺点
上手成本
总结
ORM类型框架在易用性上面做的比原生Sql提升了很多,而且在性能上GreenDao在某些方面甚至比原生的还要出色,比较适合大部分项目的开发工作。
Realm
Realm框架不是基于 SQLite 创建,它建立了自己独特的数据库存储引擎,在某些方面有自己独特的优势。
GreenDao vs Realm
基本用法
增:
Realm realm=Realm.getDefaultInstance();
realm.beginTransaction();
User user = realm.createObject(User.class); // Create a new object
user.setName("John");
user.setEmail("john@corporation.com");
realm.commitTransaction();
删:
Realm mRealm=Realm.getDefaultInstance();
final RealmResults<Dog> dogs= mRealm.where(Dog.class).findAll();
mRealm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
Dog dog=dogs.get(5);
dog.deleteFromRealm();
//删除第一个数据
dogs.deleteFirstFromRealm();
//删除最后一个数据
dogs.deleteLastFromRealm();
//删除位置为1的数据
dogs.deleteFromRealm(1);
//删除所有数据
dogs.deleteAllFromRealm();
}
});
优点
- 易用
- 快速
- 跨平台
- 可视化
缺点
- 显著增加安装包大小,增加大概4、5兆
- 数据类型限制,必须继承RealmObject、不支持内部类、修改了部分类型、不支持键值自增长。
- 线程限制,如果在UI线程获取到了当前Realm对象,在异步线程中使用当前Realm对象进行操作,就会抛出异常。
总结
如果数据量没有达到SQLite的性能瓶颈的话,建议选择基于SQLite的数据库,如果不喜欢编写原生Sql语句就牺牲一点性能去适应业务快速迭代。
这些框架同时存在都有其道理,性能有优势兼容性和稳定性不能保证,易用性很好必然就不能做太多的定制化操作,各取所需,对于一般的业务GreenDao便是一个比较好的方案。