Android Jetpack架构组件(七)— Room

2022-07-17  本文已影响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代表着一张表,而每张表都需要一个Dao对象,以方便对这张表进行各种操作(增删改查)

常用的注解

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

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这几个对象:

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 操作。

上一篇下一篇

猜你喜欢

热点阅读