前言

Room相比GreenDao而言是官方推荐的一个关于数据库的依赖库,Room更需要开发人员有较专业的SQL数据库知识,它涉及到SQL的语法编写和SQL数据库的升级,如果对SQL语法不懂的开发者来说,使用起来是很有难度的,但对于熟悉SQL语法的开发者来说,用起来比GreenDao好用许多

Room的简介

Room是Google提供的一个ORM库。Room提供了三个主要的组件:

  • @Database:@Database用来注解类,该类必须是继承自RoomDatabase的抽象类。该类主要作用是创建数据库和创建Dao
  • @Entity:@Entity用来注解实体类,@Database通过entities属性引用被@Entity注解的类,并利用该类所有字段作为表的结构
  • @Dao:@Dao用来注解一个接口或者抽象方法,该类的作用是提供访问数据库的方法

以上各部分的依赖关系如下图所示:

Android Room根据字段更新 android room升级_Room

Room的配置

配置比较简单,但是这里要注意的是,用的是kapt,如果用annotationProcess会报生成的类找不到,因为我这里用的是kotlin语言。由于很多项目用的是多Module的依赖形式,如果使用room需要跨module的话,需要使用api去替代implementation

implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation "android.arch.persistence.room:runtime:1.1.1"
implementation "android.arch.persistence.room:rxjava2:1.1.1"
kapt "android.arch.persistence.room:compiler:1.1.1"

Room的使用

下面就以学生信息的实战来使用room,这里的学生信息展示图如下

Android Room根据字段更新 android room升级_数据库_02

一、创建Bean对象(表名和字段名)

room的创建通过注解去生成表的结构,主要由下面几个注解形成

  • @Entity:表示需要持久化的实体,后面参数表示表名
  • @PrimaryKey:表示表中的主键
  • @ColumnInfo:表示表中的字段
  • @Embedded:表示表中需要嵌套的对象实体
  • @Ignore:表示表中不需要持久化的字段

特別需要注意的是:在定义的时候,记得对实体对象加上PrimaryKey,否则程序会报错

@Entity(tableName = "tb_student")
public class Student {

    @PrimaryKey(autoGenerate = true)
    public long id;

    @ColumnInfo(name = "name")
    public String name;

    @ColumnInfo(name = "sex")
    public int sex;

    @Embedded
    public StudentExtendInfo extendInfo;

    @Ignore
    public String phone;

    public static class StudentExtendInfo {
        @ColumnInfo(name = "father_name")
        public String father;
        @ColumnInfo(name = "mother_name")
        public String mother;
    }
}

二、定义数据库的增删改查

数据库的增删改查都通过注解表示,可以对具体的操作书写具体的SQL语句。由于room可以和Rxjava一起使用,所以在查询的时候可以返回Flowable

  • @Dao:表示当前接口为数据库的操作接口
  • @Query:表示查询操作,需要书写具体的SQL语句
  • @Insert:表示插入操作,需要在参数中填写插入时发生冲突时的策略
  • @Delete:表示删除操作
  • @Update:表示修改操作
@Dao
public interface StudentDaoApi {

    @Query("SELECT * FROM TB_STUDENT")
    Flowable<List<Student>> query();

    @Query("SELECT * FROM TB_STUDENT WHERE id IN (:ids)")
    Flowable<List<Student>> queryByIds(long[] ids);

    @Query("SELECT * FROM TB_STUDENT WHERE id = (:id) LIMIT 1")
    Flowable<Student> queryById(long id);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insert(Student... entities);

    @Delete
    void delete(Student entity);

    @Update
    void update(Student entity);
}

三、创建数据库

数据库的创建也是通过注解@Database生成,在注解中填写需要操作的表结构和版本。在这里我们通过kt的语言,用单例的方式去实现当前的数据库,并且要继承RoomDatabase

@Database(entities = [Student::class], version = 1)
abstract class AppDatabaseBuilder : RoomDatabase() {

    abstract val studentDao: StudentDaoApi

    companion object {
        private var INSTANCE: AppDatabaseBuilder? = null

        fun getInstance(context: Context): AppDatabaseBuilder {
            if (INSTANCE == null) {
                synchronized(AppDatabaseBuilder::class.java) {
                    
                    // 生成数据库文件
                    val builder = Room.databaseBuilder(context.applicationContext,
                            AppDatabaseBuilder::class.java, "db_common.db")

                    if (!BuildConfig.DEBUG) {
                        // 迁移数据库如果发生错误,将会重新创建数据库,而不是发生崩溃
                        builder.fallbackToDestructiveMigration()
                    }
                    INSTANCE = builder.build()
                }
            }
            return INSTANCE!!
        }
    }
}

四、使用数据库

在使用的时候,只需要获取对应的Dao接口就行操作即可

AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().query();
AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().queryByIds(ids);
AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().insert(student);
AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().delete(student);
AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().update(student);

但是我们发现query操作有返回Rxjava的特性,而插入、删除、修改并没有,我们可以通过再增加一层封装,让插入、删除、修改也支持Rxjava的特性

public class StudentDao {

    private StudentDao() {

    }

    public static StudentDao getInstance() {
        return SingletonHolder.sInstance;
    }

    private static class SingletonHolder {
        private static final StudentDao sInstance = new StudentDao();
    }

    public Flowable<List<Student>> query(Context context) {
        return AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().query();
    }

    public Flowable<List<Student>> queryByIds(Context context, long[] ids) {
        return AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().queryByIds(ids);
    }

    public Flowable<Student> queryById(Context context, long id) {
        return AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().queryById(id);
    }

    public Observable<Boolean> insert(final Context context, final Student student) {
        return Observable.fromCallable(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().insert(student);
                return true;
            }
        });
    }

    public Observable<Boolean> delete(final Context context, final Student student) {
        return Observable.fromCallable(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().delete(student);
                return true;
            }
        });
    }

    public Observable<Boolean> update(final Context context, final Student student) {
        return Observable.fromCallable(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().update(student);
                return true;
            }
        });
    }
}

封装过后的使用就贴近Rxjava的特性,需要注意的是对数据库的操作是需要异步操作的,这在Rxjava也是特别简单

StudentDao.getInstance().insert(this, student)
            .subscribeOn(Schedulers.io())
            .subscribe(
                    { Log.e("TAG", it.toString()) },
                    { Log.e("TAG", it.toString()) }
            )

五、升级数据库

在使用的过程中,经历了一次发版后,发现需要对原来的数据库表的结构进行修改,这个时候就需要我们掌握升级的SQL语法,room也提供了比较人性化的升级方式Migration,当然也逃不过SQL语法的编写

在升级的时候,不要忘记将版本号进行更新到新的版本号

@Database(entities = [Student::class], version = 2)
abstract class AppDatabaseBuilder : RoomDatabase() {

    abstract val studentDao: StudentDaoApi

    companion object {
        private var INSTANCE: AppDatabaseBuilder? = null

        fun getInstance(context: Context): AppDatabaseBuilder {
            if (INSTANCE == null) {
                synchronized(AppDatabaseBuilder::class.java) {
                
                    // 生成数据库文件
                    val builder = Room.databaseBuilder(context.applicationContext,
                            AppDatabaseBuilder::class.java, "db_common.db")
                            .addMigrations(MIGRATION_1_2, MIGRATION_2_3) // 升级数据库

                    if (!BuildConfig.DEBUG) {
                        //迁移数据库如果发生错误,将会重新创建数据库,而不是发生崩溃
                        builder.fallbackToDestructiveMigration()
                    }
                    INSTANCE = builder.build()
                }
            }
            return INSTANCE!!
        }

        /**
         * 版本1升级到2的SQL语句
         */
        private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE 'tb_student' ADD COLUMN 'Sid' INTEGER NOT NULL DEFAULT 0")
            }
        }
        
        /**
         * 版本2升级到3的SQL语句
         */
        private val MIGRATION_2_3: Migration = object : Migration(2, 3) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE 'tb_student' ADD COLUMN 'Stext' TEXT NOT NULL DEFAULT ''")
            }
        }
    }
}

结语

在Room的使用中更结合了新技术Rxjava的使用,可见Rxjava也越来越得到重视,不仅如此,SQL语法也给大家提个醒要去掌握基础的操作,否则room使用起来是很困难的。毫无疑问,google已经将Rxjava和SQL语法当做Android程序员必备的知识,所以不懂得这方面的同学,要加紧补回来哦