美文网首页
Android Jetpack架构组件(七)— Room

Android Jetpack架构组件(七)— Room

作者: 独自闯天涯的码农 | 来源:发表于2022-07-17 21:09 被阅读0次

    一、Room简介

    ROOM:轻量级 ORM 数据库,本质上是一个SQLite。

    ORM(Object Relational Mapping):对象关系映射
    该模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。

    Android采用Sqlite作为数据库存储。Sqlite代码写起来繁琐且容易出错,所以开源社区里逐渐出现了各种ORM(Object Relational Mapping)库。这些开源ORM库都是为了方便Sqlite的使用,包括数据库的创建,升级,增删改查等。常见的ORM有ORMLite,GreenDAO等。Google也意识到了推出自家ORM的必要性,于是有了Room。

    二、Room的使用

    1、在app的build.gradle文件中导入依赖

    dependencies {
        // room
        implementation "androidx.room:room-runtime:2.3.0"
        implementation "androidx.room:room-ktx:2.3.0"
        kapt 'androidx.room:room-compiler:2.3.0'
    
        // room-rxjava
        implementation "androidx.room:room-rxjava2:2.3.0"
        implementation "io.reactivex.rxjava2:rxandroid:2.1.0"
    
        // liveData
        implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0'
    
        // ViewModel
        implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0'
    }
    

    2、Room的几个概念

    • Entity:
      这是一个Model类,对应于数据库中的一张表。Entity类是Sqlite表结构在Java类的映射。

    • Dao(Data Access Objects):
      数据访问对象,顾名思义,我们可以通过它来访问数据。

    一个Entity代表着一张表,而每张表都需要一个Dao对象,以方便对这张表进行各种操作(增删改查)

    常用的注解

    Room通过一些注解来标注一些类的作用,比如Database,Dao等等。

    • @Database(entities =[],version=0)
      这个是用于标记DataBase初始化的,被标注的这个类会继承RoomDatabase,entities是一个集合,里面是所有数据库表的类。version代表数据库的版本。
    • @Entity
      代表数据库表 里面包含 @PrimaryKey主键等等注解
    • @Dao
      代表数据库操作类,里面可以使用@Query查询,@Insert插入,@Update修改,@Delete删除等注解

    3、创建DataBase类

    这个类被@Database标记,继承RoomDataBase,这个类是数据库类,数据库的版本,升级和各项初始化配置都在这里进行。

    /**
     * (1) 使用entities来映射相关的实体类
     * (2) version来指明当前数据库的版本号
     * (3) 使用了单例模式来返回Database,以防止新建过多的实例造成内存的浪费
     *(4)Room.databaseBuilder(context,klass,name).build()来创建Database,
     *    其中第一个参数为上下文,
     *    第二个参数为当前Database的class字节码文件,
     *    第三个参数为数据库名称
     * 注意事项:默认情况下Database是不可以在主线程中进行调用的。
     *          因为大部分情况,操作数据库都还算是比较耗时的动作。
     *          如果需要在主线程调用则使用allowMainThreadQueries进行说明。
     */
    @Database(entities = [UserEntity::class], version = 1)
    abstract class AppDatabase : RoomDatabase() {
    
        abstract fun userDao(): UserDAO
    
    }
    

    4、创建表

    被注解@Entity标记,代表这个是一个数据库表,@PrimaryKey为主键

    /**
     * (1) @Entity注解中我们传入了一个参数 tableName用来指定表的名称,如果不传默认类名为表名
     *(2)@PrimaryKey注解用来标注表的主键,并且使用autoGenerate = true 来指定了主键自增长
     *(3)@ColumnInfo注解用来标注表对应的列的信息比如表名、默认值等等
     *(4)@Ignore 注解顾名思义就是忽略这个字段,使用了这个注解的字段将不会在数据库中生成对应的列信息。也可以使用@Entity注解中的 ignoredColumns 参数来指定,效果是一样的
     */
    @Entity(tableName = "users")
    class UserEntity(
        @PrimaryKey(autoGenerate = false) var userId: String,
        @ColumnInfo(name = "user_name") var userName: String,
        @ColumnInfo(name = "avatar") var avatar: String
    )
    

    5、创建Dao

    被@Dao注解标记,这个是数据库操作类,可以使用增删改查的一些注解标记方法。
    @Query:为查询表中的数据的意思
    @Update:修改的表中的数据的意思
    @Insert:插入新的表数据
    @Delete: 删除表的数据

    @Dao
    interface UserDAO {
    
        @Query("select * from users where userId = :id")
        fun getUserById(id: Long): UserEntity
    
        @Query("select * from users")
        fun getAllUsers(): List<UserEntity>
    
        // 参数onConflict,表示的是当插入的数据已经存在时候的处理逻辑,有三种操作逻辑:REPLACE、ABORT和IGNORE。
        // 如果不指定则默认为ABORT终止插入数据。这里我们将其指定为REPLACE替换原有数据。
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        fun addUser(user: UserEntity)
    
        @Delete
        fun deleteUserByUser(user: UserEntity)
    
        @Update
        fun updateUserByUser(user: UserEntity)
    
        // 上面说的 @Query 查询接受的参数是一个字符串,所以像删除或者更新我们也可以使用
        // @Query 注解来使用SQL语句来直接执行。比如根据userid来查询某个用户或者根据userid更新某个用户的姓名:
    
        @Query("delete from users where userId = :id ")
        fun deleteUserById(id: Long)
    
        @Query("update  users set user_name = :updateName where userID =  :id")
        fun update(id: Long, updateName: String)
    }
    

    6、数据库工具类

    class DbHelper private constructor() {
        var mDatabase: AppDatabase? = null
        var currentUser: String? = null
    
        companion object {
            val instance: DbHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
                DbHelper()
            }
        }
    
        /**
         * 初始化数据库
         */
        fun initDB(context: Context, user: String) {
            if (currentUser != null) {
                if (TextUtils.equals(currentUser, user)) {
                    return
                }
                closeDb()
            }
            currentUser = user
            val dbName = "ring${currentUser}.db"
            mDatabase = Room.databaseBuilder(
                context.applicationContext,
                AppDatabase::class.java,
                dbName
            ).allowMainThreadQueries().build()
        }
    
        /**
         * 关闭数据库
         */
        fun closeDb() {
            if (mDatabase != null) {
                mDatabase!!.close()
                mDatabase = null
            }
        }
    
        /**
         * 获取用户访问对象
         */
        fun getUserDao(): UserDAO? {
            if (mDatabase != null) {
                return mDatabase!!.userDao()
            }
            return null
        }
    }
    

    7、数据库升级

    数据库升级一般是表字段进行了修改。一般新增一些字段的情况比较多。

    1.添加Entity字段

    原Entity

    @Entity(tableName = "users")
    class UserEntity(
        @PrimaryKey(autoGenerate = false) var userId: String,
        @ColumnInfo(name = "user_name") var userName: String,
        @ColumnInfo(name = "avatar") var avatar: String
    )
    

    新Entity添加一个年龄

    @Entity(tableName = "users")
    class UserEntity(
        @PrimaryKey(autoGenerate = false) var userId: String,
        @ColumnInfo(name = "user_name") var userName: String,
        @ColumnInfo(name = "avatar") var avatar: String
        @ColumnInfo(name = "age") var age: Int
    )
    
    2.修改AppDataBase的版本号

    原来的版本号:

    @Database(entities = [UserEntity::class], version = 1)
    

    修改后的版本号:

    @Database(entities = [UserEntity::class], version = 2)
    

    注意:version的版本号要和下面提到的MIGRATION要对应上!

    3.创建MIGRATION

    添加一个MIGRATION,并且添加上修改数据库表字段的sql代码:

        private val MIGRATION_1_2 = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE User ADD COLUMN age Interge")
            }
        }
    

    注意: Migration(1, 2)代表数据库版本从1升级到2,和前一步修改数据库版本对应。

    4.将创建的MIGRATION添加到数据构造中去
         /**
         * 初始化数据库
         */
        fun initDB(context: Context, user: String) {
            if (currentUser != null) {
                if (TextUtils.equals(currentUser, user)) {
                    return
                }
                closeDb()
            }
            currentUser = user
            val dbName = "ring${currentUser}.db"
            mDatabase = Room.databaseBuilder(
                context.applicationContext,
                AppDatabase::class.java,
                dbName
            )
              // MIGRATION添加到这里
              .addMigrations(MIGRATION_1_2)
              .allowMainThreadQueries()
              .build()
        }
    
            // MIGRATION
            private val MIGRATION_1_2 = object : Migration(2, 3) {
                override fun migrate(database: SupportSQLiteDatabase) {
                    database.execSQL("ALTER TABLE User ADD COLUMN age Interge")
                }
            }
    

    如果有多个MIGRATION则往后添加:

     .addMigrations(MIGRATION_1_2, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7)
    

    三、Room和Rxjave、LiveDate、Flow联用

    1、Room与LiveData联用

    class UserViewModel: ViewModel() {
    
        // 获取用户列表的LiveData
        var userLiveData: MutableLiveData<MutableList<UserEntity>> = MutableLiveData()
    
        fun getUserList(context: Context) {
            viewModelScope.launch {
                val list = AppDataBase.getInstance(context).userDao().getUsers()
                userLiveData.postValue(list)
            }
        }
    }
    
        private fun getUserList() {
            viewModel.userLiveData.observe(this , Observer {
                //获取到用户列表
            })
        }
    

    2、Room与Rxjava联用

    如果要和Rxjava联用可以需要添加依赖:

    implementation "androidx.room:room-rxjava2:2.3.0"
    

    dao的方法需要改成这样:

        /**
         * 和Rxjava联用的获取数据
         */
        @Query("SELECT * FROM user")
        fun getUsersByRxjava() :Single<MutableList<UserEntity>>
    

    使用的时候:

        private fun getUserListByRxJava() {
            var disposable = AppDataBase.getInstance(this)
                .userDao().getUsersByRxjava().subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                    //获取到用户列表
                }, {
    
                })
            mDisposables?.add(disposable)
        }
    

    Insert、update、delete操作可以返回Complete,Single,Maybe三种对象;
    query操作可以返回Single,Maybe,也可以返回Observable,Flowable。

    RxJave这几个对象:

    • Completable:
      只有onComplete和onError方法,即是只有“完成”和“错误”两种状态,不会返回具体的结果。
    • Single:
      其回调为onSuccess和onError,查询成功会在onSuccess中返回结果,需要注意的是,如果未查询到结果,即查询结果为空,会直接走onError回调,抛出EmptyResultSetException异常。
    • Maybe:
      其回调为onSuccess,onError,onComplete,查询成功,如果有数据,会先回调onSuccess再回调onComplete,如果没有数据,则会直接回调onComplete。
    • Flowable/Observable:
      返回一个可观察的对象,每当查询语句查询的部分有变化时,都会回调它的onNext方法,直到Rx流断开。

    3、Room与Flow联用

    dao的方法需要改成这样:

    @Query("select * from users where userId = :id")
    fun getUserById(id: Long): Flow<UserEntity>
    
    //直接拉取显示
    private fun getUserListByRxJava() {
            lifecycleScope.launch {
                //flow
                AppDataBase.getInstance(this).userDao(). getUserById(1)
                    .collect {
                        //获取数据UI 显示
                    }
            }
        }
    

    注意:只要数据库任意一个数据改变都会重新执行 query 操作并再次派发 Flow,这是因为 SQLite 数据库的内容更新通知功能是以表 (Table) 数据为单位,而不是以行 (Row) 数据为单位,因此只要是表中的数据有更新,它就触发内容更新通知。Room 不知道表中有更新的数据是哪一个,因此它会重新触发 DAO 中定义的 query 操作。

    相关文章

      网友评论

          本文标题:Android Jetpack架构组件(七)— Room

          本文链接:https://www.haomeiwen.com/subject/hfukirtx.html