> 文档中心 > Room数据库实战:搭配RxJava使用与封装

Room数据库实战:搭配RxJava使用与封装


一、介绍

接着上一篇Room的基本使用介绍(不会Room基本使用的先看这一篇),每次使用增删改查功能都需要new Thread,不方便也不好管理,本章主要介绍RxJava如何搭配Room使用。

二、引入RxJava库

def latest_version = "2.2.5"//roomimplementation "androidx.room:room-runtime:$latest_version"implementation "androidx.room:room-ktx:$latest_version"kapt "androidx.room:room-compiler:$latest_version"// optional - RxJava support for Roomimplementation "androidx.room:room-rxjava2:$latest_version"

三、项目实战

使用前需要先准备数据实体数据库数据访问对象(DAO)。 其中数据实体和数据库的内容与上一篇的内容一样,核心在数据访问对象的修改

数据实体

@Entityclass RunRecord {    @PrimaryKey(autoGenerate = true)    var id: Long? = null @ColumnInfo(name = "userId")    var userId: String? = null @ColumnInfo(name = "pathLine")    var pathLine: String? = null @ColumnInfo(name = "totalStep")    var totalStep:Int? = 0}

数据库

@Database(entities = [ RunRecord::class], version = 1)abstract class YzDatabase : RoomDatabase() {    abstract fun runDao(): RunDao?    companion object { private const val DB_NAME = "yzWill.db" @Volatile private var instance: YzDatabase? = null fun getInstance(context: Context) =     instance ?: synchronized(this) {  instance ?: Room.databaseBuilder(      context,      YzDatabase::class.java, DB_NAME  ).build()     }    }}

数据访问对象

与上一篇的区别就是把方法的返回值外包装了一层Single(为什么使用Single后面说),例如:Long ->Single

@Daointerface RunDao{    /     *     * 插入一条跑步信息,id不用传,会自动增长.建议不传     * @param info RunRecord?     * @return Single  返回插入的主键id     */    @Insert    fun insert(info: RunRecord?): Single<Long>    /     * 删除某一条,需要传入主键ID 例如,:    RunRecord().apply {    this.id = 1    }     * @return Single  1 成功 0失败     */    @Delete    fun delete(info: RunRecord?): Single<Int>    /     * 需要传入主键ID 例如,传入id,修改userId为 "weng":    RunRecord().apply {    this.id = 1    this.userId = "weng"    }     * @return Single  1 成功 0失败     */    @Update    fun update(vararg info: RunRecord?): Single<Int>    /     * 根据userId获取最后一条     * @param userId String     * @return RunRecord?     */    @Query("SELECT * FROM RunRecord WHERE userId LIKE :userId order by userId desc  LIMIT 1")    fun findLastByUserId(userId: String): Single<RunRecord?> /     * 查询全部数据     */    @get:Query("SELECT * FROM RunRecord")    val all: Single<List<RunRecord?>?>}

使用

下面就举两个插入和删除的栗子

  val db = YzDatabase.getInstance(this).runDao() //插入一条数据,返回主键id db?.insert(RunRecord().apply {     pathLine = "插入一条信息"     userId = "小小虫" })     ?.subscribeOn(Schedulers.io())     ?.observeOn(AndroidSchedulers.mainThread())     ?.doOnSuccess {  Log.e(tag, "id:$it")     }?.subscribe()
  val db = YzDatabase.getInstance(this).runDao() //删除ID为1的数据,返回 0:删除失败 1:删除成功 db?.delete(RunRecord().apply {     id = 1     pathLine = "插入一条信息"     userId = "小小虫" })     ?.subscribeOn(Schedulers.io())     ?.observeOn(AndroidSchedulers.mainThread())     ?.doOnSuccess {  Log.e(tag, "success:${it == 1}")     }?.subscribe()

四、封装

通过上文,我们可以虽然把Room结合了RxJava,但用起来很明显有很多冗余的代码,可以简单的封装一下。增加一个DBManager类,如下:

class DBManager(context: Context) {    private var mDB: YzDatabase? = YzDatabase.getInstance(context)    companion object { @Volatile private var instance: DBManager? = null fun getInstance(context: Context) =     instance ?: synchronized(this) {  instance ?: DBManager(context)     }    }    fun insertRunRecord(info: RunRecord): Single<Long>? { return extra(mDB?.runDao()?.insert(info))    }  fun findLastByUserId(userId: String): Single<RunRecord?>? { return extra(mDB?.runDao()?.findLastByUserId(userId))    }    private fun <T> extra(single: Single<T>?): Single<T>? { return single     ?.subscribeOn(Schedulers.io())     ?.observeOn(AndroidSchedulers.mainThread())    }}

使用

可以看到,代码量明显的减少。

 DBManager.getInstance(this).insertRunRecord(RunRecord().apply {     pathLine = "插入一条信息"     userId = "小小虫" }) ?.doOnSuccess {     Log.e(tag, "id:$it") }?.subscribe()

五、踩坑

先看一段查询数据库的代码,如下:

DBManager.getInstance(this).findLastByUserId(userId = "小小虫")   ?.doOnSuccess {     Log.e(tag, "info:$it") }?.subscribe()

一段很简单的代码,但问题是,如果你查到数据了,一切都好。如果查不到,对不起,App闪退,报错如下:

在这里插入图片描述

解决方法如下:

  1. 不要使用RxJava(。。。),也就是用最原始的方法,new Thread去查询。
  2. 不要使用Single接收,使用Maybe(推荐)或者Flowable、Observable。
  3. 如果你还要用Single,那么请在包一层List,也就是Single。

看下官方的解释:
在这里插入图片描述

至于这个问题的原因,其实跟Single的特性相关,因为Single本身的设计就是必须发出单个非空值的类型。

为什么使用Single?

RxJava的被观察者有五个,如下:

  • Observable: 用来发射0或者多个数据
  • Flowable: Observable的升级版,增加了背压策略
  • Single: 用来发射单个数据onSuccess或错误onError事件
  • Maybe: 用来发射0或者单个数据
  • Completable: 不发射数据,Room好像也不支持()

可以看到,数据库的增加改查都是需要0次或者单次的结果返回的,那么就只需要在Single和Maybe中选择。当然,你使用Observable和Flowable也是可以的,这里推荐使用Maybe来作为查询数据的返回,效率相比其他两种会比较快。