ORM 表示全称为对象关系映射(Object Relational Mapping)。
O 可以理解为java对象
R 可以理解为关系型数据库(sqllite)
M 可以理解为从java对象到关系型数据库建立映射关系的过程

ORM是什么

ORM 表示全称为对象关系映射(Object Relational Mapping)。
O 可以理解为java对象
R 可以理解为关系型数据库(sqllite)
M 可以理解为从java对象到关系型数据库建立映射关系的过程
是一种为了解决面向对象与关系型数据库存在不匹配现象的技术,简单说,orm通过描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。

为什么使用ORM

存在即合理

ORM框架的存在大大提高了开发效率,ORM框架会自动为我们生成对象到数据库建立映射关系的逻辑,不需要我们在重复的进行代码编写。如下代码ORM框架会为我们自动生成,是不是很方便。

   final Cursor _cursor = __db.query(_statement);
    try {
      final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
      final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
			 final Bean _result;
      if(_cursor.moveToFirst()) {
        _result = new Bean();
        _result.id = _cursor.getInt(_cursorIndexOfId);
        _result.name = _cursor.getString(_cursorIndexOfName);
      } else {
        _result = null;
      }
      return _result;

ORM框架选型

没有最好,只有最合适

目前业内有很多ORM框架如ORMLite, Sugar ORM, Freezer, DBFlow, GreenDAO,Room等,如何在众多中的框架中进行抉择呢?我们主要从性能和使用方式进行对比,选择最合适的。

ORM框架性能对比

性能对比主要是测试Orm框架增删改查的耗时。这里github上的一个开源项目已经明确列出了各个ORM框架的性能对比数据。 详情地址

通过数据我们看出GreenDAO, Room框架的整体性能是很出色的。所以最终我们选择这两个框架中进行选择。

使用方式对比

对于现有的ORM框架在使用方式上其实大同小异,我们在table映射,数据库初始化,增删改查操作,数据库升级和与Rxjava配合使用,这5点进行对比。

GreenDao框架

GreenDao框架目前存在两种使用方式。

  1. 一种是通过引入GreenDao的Gradle插件来生成所需要的类,
  2. 一种是新建一个java工程通过GreenDao的generator来构造所需要的类。
    这种方式适合如下场景:你的应用内有多个数据库( multiple schemas)的话,那只能使用第二种方式,新建个java工程,然后编写数据库结构代码,运行工程后生成我们需要的操作数据库代码,最后拷贝到项目工程中。

In most cases, we recommend to use the new Gradle plugin instead. However, some advanced features are currently only supported by the generator. For example multiple schemas.

本文主要讲解第一种使用方式。

框架引入

工程中引入GreenDao插件,同时我们还需要指定数据库版本号,生成代码的包名和对应的代码路径。

// In your root build.gradle file:
buildscript {
    repositories {
        jcenter()
        mavenCentral() // add repository
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.1'
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin
    }
}
 
// In your app projects build.gradle file:
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao' // apply plugin

greendao {
    schemaVersion 1 //数据库版本号
    daoPackage 'com.speedystone.greendaodemo.db'// 设置DaoMaster、DaoSession、Dao 包名
    targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录
}

dependencies {
    implementation 'org.greenrobot:greendao:3.2.2' // add library
}

table映射

Java Bean对象与数据库中的table结构建立映射关系,需要我们在对应的bean上添加@Entity即可

@Entity
public class User {
    @Id(autoincrement = true)
    Long id;
		@Property (nameInDb="age")
    int age; 
		@Property (nameInDb="name")
    String name;
		@Property (nameInDb="address")
    String address;
    }

数据库初始化

我们可以构建一个DaoSession的全局单例,可以通过操作DaoSession获取每个表的对应的Dao操作对象,进行增删改查操作。

 /**
     * 初始化GreenDao,直接在Application中进行初始化操作
     */
    private void initGreenDao() {
        DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "test.db");
        SQLiteDatabase db = helper.getWritableDatabase();
        DaoMaster daoMaster = new DaoMaster(db);
        daoSession = daoMaster.newSession();
    }
    
    private DaoSession daoSession;
    public DaoSession getDaoSession() {
        return daoSession;
    }

增删改查操作

  1. 添加数据
daoSession.insert(user); //插入数据
daoSession.insertOrReplace(user);//插入或替换
  1. 删除数据
daoSession.delete(user);
daoSession.deleteAll(User.class);
  1. 修改数据
daoSession.update(user);
  1. 查询数据
    方式1:loadAll():查询所有数据。
    方式2:queryRaw():根据条件查询。
    方式3:queryBuilder() : 方便查询的创建,后面详细讲解。
 List<User> users = daoSession.loadAll(User.class); //方式1
 List<User> users = daoSession.queryRaw(User.class, " where id = ?", s); //方式2
   QueryBuilder<User> qb = daoSession.queryBuilder(User.class);
        List<User> list = qb.list(); // 方式3
 

方式3的使用方式比较繁琐,queryBuilder定义了很多sql语句对应的方法如 eq(),like(),between()等等。

数据库升级

GreenDao的升级思路:

  1. 创建临时表TMP_,复制原来的数据库到临时表中;
  2. 删除之前的原表;
  3. 创建新表;
  4. 将临时表中的数据复制到新表中,最后将TMP_表删除掉;
    GreenDao数据库升级步骤较为复杂繁琐,并且效率比较低。

与Rxjava配合使用

GreenDao支持与Rxjava一起使用每个Dao都有rx()方法返回Observable进行链式调用和线程切换操作。

GreenDaoUtil.getDaoSession(activity)
            .getUserDao()
            .queryBuilder()
            .where(UserDao.Properties.Age.gt(30))
            .rx()
            .list()
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                new Action1<List<User>>() {
                    @Override
                    public void call(List<User> peopleBeen) {
                    }
                }, 
                new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        throwable.printStackTrace();
                    }
                }
            );

ROOM框架


ROOM框架是Google在2017年Google IO大会上推出的官方数据框框架。ROOM框架基于APT(Annotation Processing Tool),我们通过使用注解然后重新build一下Moudle工程,就会自动生成我们需要的代码。

框架引入

直接在对应的Moudle添加依赖即可

dependencies {
 api "android.arch.persistence.room:runtime:$1.1.1"
 annotationProcessor "android.arch.persistence.room:compiler:$1.1.1"
}

table映射

使用Entity注解,在每个属性中需要通过ColumnInfo制定数据库中对应的字段名称

@Entity(indices = {@Index(value = {"first_name", "last_name"},
        unique = true)})
class User {
    @PrimaryKey
    public int id;
    @ColumnInfo(name = "first_name")
    public String firstName;
    @ColumnInfo(name = "last_name")
    public String lastName;
}

数据库初始化

建立数据库映射关系使用Database注解,指定该数据库中含有的table和当前数据库版本号

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

    private static volatile AppDatabase INSTANCE;

    public abstract UserDao userDao();

    public static AppDatabase getInstance(Context context) {
        if (INSTANCE == null) {
            synchronized (AppDatabase.class) {
                if (INSTANCE == null) {

                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                            AppDatabase.class, "sample.db")
                            .build();
                }
            }
        }
        return INSTANCE;
    }

}

数据增删改查操作

对每个数据表的操作,我们均需要定义对应的Dao,并且支持响应式查询。添加方式如下。

@Dao
public interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public void insertUsers(User... users);
		
    @Query("select * from user")
    List<User> getAll();
}

数据库升级

每次升级数据库添加Migration即可,实现migrate方法添加数据库对应的修改逻辑

Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
        .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
                + "`name` TEXT, PRIMARY KEY(`id`))");
    }
};

static final Migration MIGRATION_2_3 = new Migration(2, 3) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE Book "
                + " ADD COLUMN pub_year INTEGER");
    }
};

与Rxjava2 配合使用

由于Room是不能在主线程进行数据库操作的,一在主线程操作,系统就会用java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time. 所以Room支持与Rxjava2配合使用,支持Completable,Single,Maybe,Observable和Flowable

@Query(“SELECT * FROM Users WHERE id = :userId”)
Single<User> getUserById(String userId);

对比总结

通过在使用方式对比,整体上GreenDao与Room在使用方式上差异并不是很大,最终决定选择Room框架,主要存在如下几点:

  1. Room是基于APT(Annotation Processing Tool),而GreenDao是基于插件来实现自动生成代码。每次数据库的变动都会重新生成代码,而Room框架基于APT生成的代码在build目录中的apt目录下,对开发者来说是无感知的。
  2. 对应增删改查的操作,Room框架更加灵活,只要我们熟练的掌握sql语法即可,而GreenDao对于复杂的查询操作则需要使用GreenDao包装的函数
  3. 数据库升级方面Room框架更加灵活,效率更加高效
  4. Room对多数据库模式的支持更加友好。app内一般都会存在多个数据库。而GreenDao需要新建个java工程才可以支持多数据库模式,然后拷贝代码到app工程当中。Room则只需要多添加Database注解即可。

应用

大道至简,知易行难

基于以上,我们最终选择了Room框架,但是接入的过程中还是遇到了一些问题,在这里记录一下,以防后来的人在踩不必要的坑。

  1. 不能忽视的@Ignore注解。
    迁移的过程中我们大多会在原有的bean对象的基础上去建立映射关系,也许之前我们定义的bean并不能与数据库的结构一一对应,所以需要我们对于那些不能对应的字段属性上添加@Ignore注解
  2. 对于多个合并在一起的操作,考虑是否使用事务
  3. 混淆配置问题
    Room框架的官方文档和demo中并没有混淆配置的说明,需要注意开启混淆的话需要对集成RoomDatabase的类不要进行混淆。
-keep class * extends android.arch.persistence.room.RoomDatabase { *; }
  1. 跨进程问题。GreenDao与Room框架都是不支持跨进程的,如果需要支持跨进程操作建议使用ContentProvider。

参考

http://greenrobot.org/greendao/

https://developer.android.com/training/data-storage/room/index.html

https://github.com/AlexeyZatsepin/Android-ORM-benchmark