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闪退,报错如下:
解决方法如下:
- 不要使用RxJava(。。。),也就是用最原始的方法,new Thread去查询。
- 不要使用Single接收,使用Maybe(推荐)或者Flowable、Observable。
- 如果你还要用Single,那么请在包一层List,也就是Single。
看下官方的解释:
至于这个问题的原因,其实跟Single的特性相关,因为Single本身的设计就是必须发出单个非空值的类型。
为什么使用Single?
RxJava的被观察者有五个,如下:
- Observable: 用来发射0或者多个数据
- Flowable: Observable的升级版,增加了背压策略
- Single: 用来发射单个数据onSuccess或错误onError事件
- Maybe: 用来发射0或者单个数据
- Completable: 不发射数据,Room好像也不支持()
可以看到,数据库的增加改查都是需要0次或者单次的结果返回的,那么就只需要在Single和Maybe中选择。当然,你使用Observable和Flowable也是可以的,这里推荐使用Maybe来作为查询数据的返回,效率相比其他两种会比较快。