数据库到底哪家强?

阅读 2956
收藏 67
2017-04-18
原文链接:www.jackywang.tech

目前大部分开源数据库都是基于SQLite发展而来,如SQLBrite、OrmLite、DBFlow、GreenDao等等,还有一个是Realm框架不是基于 SQLite 创建,它建立了自己独特的数据库存储引擎。那这么多框架到底有什么区别,那种更适合自己的产品呢?

我们先看基于SQLite发展而来的框架,这方面主要分成两条发展路线,一条是Rx或者叫做对象映射(OM)路线,一条是对象关系映射(ORM)路线。

Rx/对象映射路线

代表数据库有sqlbritesqldelight,这两个都是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 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 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便是一个比较好的方案。

评论