Jetpack之Room的使用,结合Flow,app架构师

本文详细介绍了如何在Android中使用Room库进行数据库操作,包括创建数据库和DAO,定义Entity,以及使用Migration进行数据库升级。示例展示了如何进行数据的增删改查,以及使用协程处理数据库操作,确保不阻塞UI线程。同时强调了单例模式在数据库实例化中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

testImplementation “androidx.room:room-testing:$room_version”

}




[]( )主要组件

===============================================================



*   `数据库`:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。  

    使用 `@Database`注释的类应满足以下条件:

    *   是扩展 `RoomDatabase` 的抽象类。

    *   在注释中添加与数据库关联的实体列表。

    *   包含具有 0 个参数且返回使用`@Dao`注释的类的抽象方法。  

        在运行时,您可以通过调用 `Room.databaseBuilder()` 或 `Room.inMemoryDatabaseBuilder()`获取 `Database`的实例。

*   `Entity`:表示数据库中的表。

*   `DAO`:包含用于访问数据库的方法。



应用使用 Room 数据库来获取与该数据库关联的数据访问对象 (DAO)。然后,应用使用每个 DAO 从数据库中获取实体,然后再将对这些实体的所有更改保存回数据库中。 最后,应用使用实体来获取和设置与数据库中的表列相对应的值。



关系如图:  

![在这里插入图片描述](https://img-blog.csdnimg.cn/2021011616183828.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3llY2hhb2E=,size_16,color_FFFFFF,t_70#pic_center)



ok,基本概念了解之后,看一下具体是怎么搞的。



### []( )Entity



@Entity(tableName = “t_history”)

data class History(

/**

 * @PrimaryKey主键,autoGenerate = true 自增

 * @ColumnInfo 列 ,typeAffinity 字段类型

 * @Ignore 忽略

 */



@PrimaryKey(autoGenerate = true)

@ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)

val id: Int? = null,



@ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)

val name: String?,



@ColumnInfo(name = "insert_time", typeAffinity = ColumnInfo.TEXT)

val insertTime: String?,



@ColumnInfo(name = "type", typeAffinity = ColumnInfo.INTEGER)

val type: Int = 1

)




*   Entity对象对应一张表,使用`@Entity`注解,并声明你的表名即可

*   `@PrimaryKey` 主键,`autoGenerate = true` 自增

*   `@ColumnInfo` 列,并声明列名 ,`typeAffinity` 字段类型

*   `@Ignore` 声明忽略的对象



很简单的一张表,主要是`name`和`insertTime`字段。



### []( )DAO



@Dao

interface HistoryDao {

//按类型 查询所有搜索历史

@Query("SELECT * FROM t_history WHERE type=:type")

fun getAll(type: Int = 1): Flow<List<History>>



@ExperimentalCoroutinesApi

fun getAllDistinctUntilChanged() = getAll().distinctUntilChanged()



//添加一条搜索历史

@Insert

fun insert(history: History)



//删除一条搜索历史

@Delete

fun delete(history: History)



//更新一条搜索历史

@Update

fun update(history: History)



//根据id 删除一条搜索历史

@Query("DELETE FROM t_history WHERE id = :id")

fun deleteByID(id: Int)



//删除所有搜索历史

@Query("DELETE FROM t_history")

fun deleteAll()

}




*   @Insert:增

*   @Delete:删

*   @Update:改

*   @Query:查



这里有一个点需要注意的,就是`查询所有搜索历史`返回的集合我用`Flow`修饰了。



只要是数据库中的任意一个数据有更新,无论是哪一行数据的更改,那就重新执行 `query`操作并再次派发`Flow`。



同样道理,如果一个不相关的数据更新时,`Flow`也会被派发,会收到与之前相同的数据。



> 这是因为 SQLite 数据库的内容更新通知功能是以表 (Table) 数据为单位,而不是以行 (Row) 数据为单位,因此只要是表中的数据有更新,它就触发内容更新通知。Room 不知道表中有更新的数据是哪一个,因此它会重新触发 DAO 中定义的 query 操作。您可以使用 Flow 的操作符,比如 distinctUntilChanged 来确保只有在当您关心的数据有更新时才会收到通知。



//按类型 查询所有搜索历史

@Query("SELECT * FROM t_history WHERE type=:type")

fun getAll(type: Int = 1): Flow<List<History>>



@ExperimentalCoroutinesApi

fun getAllDistinctUntilChanged() = getAll().distinctUntilChanged()



### []( )数据库



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

abstract class HistoryDatabase : RoomDatabase() {

abstract fun historyDao(): HistoryDao



companion object {

    private const val DATABASE_NAME = "history.db"

    private lateinit var mPersonDatabase: HistoryDatabase



    //注意:如果您的应用在单个进程中运行,在实例化 AppDatabase 对象时应遵循单例设计模式。

    //每个 RoomDatabase 实例的成本相当高,而您几乎不需要在单个进程中访问多个实例

    fun getInstance(context: Context): HistoryDatabase {

        if (!this::mPersonDatabase.isInitialized) {

            //创建的数据库的实例

            mPersonDatabase = Room.databaseBuilder(

                context.applicationContext,

                HistoryDatabase::class.java,

                DATABASE_NAME

            ).build()

        }

        return mPersonDatabase

    }

}

}




*   使用`@Database`注解声明

*   `entities` 数组,对应此数据库中的所有表

*   `version` 数据库版本号



**注意:**



> 如果您的应用在单个进程中运行,在实例化 AppDatabase 对象时应遵循`单例设计模式`。 每个 RoomDatabase  

> 实例的成本相当高,而您几乎不需要在单个进程中访问多个实例。



[]( )使用

=============================================================



在需要的地方获取数据库



mHistoryDao = HistoryDatabase.getInstance(this).historyDao()




### []( )获取搜索历史



private fun getSearchHistory() {

    MainScope().launch(Dispatchers.IO) {

        mHistoryDao.getAll().collect {

            withContext(Dispatchers.Main){

                //更新ui

            }

        }

    }

}



`collect` 是`Flow`获取数据的方式,并不是唯一方式,可以[查看文档]( )。



为什么放在`协程`里面呢,因为数据库的操作是费时的,而协程可以轻松的指定线程,这样不阻塞`UI`线程。



查看Flow源码也发现,Flow是协程包下的



package kotlinx.coroutines.flow




以collect为例,也是被`suspend` 修饰的,既然支持`挂起`,那配合`协程`岂不美哉。



@InternalCoroutinesApi

public suspend fun collect(collector: FlowCollector<T>)



### []( )保存搜索记录



private fun saveSearchHistory(text: String) {

    MainScope().launch(Dispatchers.IO) {

        mHistoryDao.insert(History(null, text, DateUtils.longToString(System.currentTimeMillis())))

    }

}



### []( )清空本地历史



private fun cleanHistory() {

    MainScope().launch(Dispatchers.IO) {

        mHistoryDao.deleteAll()

    }

}



> 作者:[https://blog.youkuaiyun.com/yechaoa]( )



[]( )数据库升级

================================================================



数据库升级是一个重要的操作,毕竟可能会造成数据丢失,也是很严重的问题。



Room通过`Migration`类来执行升级的操作,我们只要告诉`Migration`类改了什么就行,比如`新增`字段或表。



### []( )定义Migration类



/**

 * 数据库版本 1->2 t_history表格新增了updateTime列

 */

private val MIGRATION_1_2: Migration = object : Migration(1, 2) {

    override fun migrate(database: SupportSQLiteDatabase) {

        database.execSQL("ALTER TABLE t_history ADD COLUMN updateTime String")

    }

}

/**

 * 数据库版本 2->3 新增label表

 */

private val MIGRATION_2_3: Migration = object : Migration(2, 3) {

    override fun migrate(database: SupportSQLiteDatabase) {

        database.execSQL("CREATE TABLE IF NOT EXISTS `t_label` (`id` INTEGER PRIMARY KEY autoincrement, `name` TEXT)")

    }

}



`Migration`接收两个参数:



*   startVersion 旧版本

*   endVersion 新版本



### []( )通知数据库更新



mPersonDatabase = Room.databaseBuilder(

    context.applicationContext,

    HistoryDatabase::class.java,

    DATABASE_NAME

).addMigrations(MIGRATION_1_2, MIGRATION_2_3)

    .build()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值