Android Room 入坑详解

Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优势:

  • 针对 SQL 查询的编译时验证。
  • 可最大限度减少重复和容易出错的样板代码的方便注解。
  • 简化了数据库迁移路径。

主要组件

Room 包含三个主要组件:

  • 数据库类,用于保存数据库并作为应用持久性数据底层连接的主要访问点。
  • 数据实体,用于表示应用的数据库中的表。
  • 数据访问对象 (DAO),提供您的应用可用于查询、更新、插入和删除数据库中的数据的方法。

数据库类为应用提供与该数据库关联的 DAO 的实例。反过来,应用可以使用 DAO 从数据库中检索数据,作为关联的数据实体对象的实例。此外,应用还可以使用定义的数据实体更新相应表中的行,或者创建新行供插入。


Room 的不同组件之间的关系

引入Room依赖

首先在build.gradle导入依赖,Kotlin和RxJava可以按需导入,注释处理工具选一个即可。

def room_version = "2.4.2"
implementation"androidx.room:room-runtime:$room_version"
// 注释处理工具
annotationProcessor "androidx.room:room-compiler:$room_version"
// Kotlin注释处理工具(kapt)
kapt"androidx.room:room-compiler:$room_version"

// kotlin扩展和协同程序对Room的支持
implementation "androidx.room:room-ktx:$room_version"
// RxJava2
implementation "androidx.room:room-rxjava2:$room_version"
// RxJava3
implementation "androidx.room:room-rxjava3:$room_version"

如果使用Kotlin注释处理工具(kapt),还需要在build.gradle文件顶部添加下方定义。

apply plugin: 'kotlin-kapt'

android 块中添加 packagingOptions 块,以从软件包中排除原子函数模块并防止出现警告。

android {
    // other configuration (buildTypes, defaultConfig, etc.)

    packagingOptions {
        exclude 'META-INF/atomicfu.kotlin_module'
    }

    // 要使用的一些 API 需要 1.8 `jvmTarget`
    kotlinOptions {
        jvmTarget = "1.8"
    }

}

创建实体

Room 允许通过实体创建表,以下代码定义了一个 User 数据实体。User 的每个实例都代表应用数据库中 user 表中的一行。

@Entity
data class User(
    @PrimaryKey val uid: Int,
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?,
    @Ignore val picture: Bitmap?
)
  • @Entity(tableName = "xxx") 每个 @Entity 类代表一个 SQLite 表。
  • @PrimaryKey 声明主键,@PrimaryKey(autoGenerate = true)可自动生成唯一的主键。
  • @ColumnInfo(name = "xxx") 可以指定表中列的名称,默认是字段名称。
  • 存储在数据库中的每个属性均需公开,这是 Kotlin 的默认设置。
  • 如果某个实体中有不想保留的字段,则可以使用 @Ignore 为这些字段添加注解

创建DAO

在 DAO(数据访问对象)中,可以指定 SQL 查询并将其与方法调用相关联,DAO 必须是一个接口或抽象类。默认情况下,所有语句都必须在单独的线程上执行。Room 支持Kotlin 协程,可使用 suspend 修饰符对查询进行注解,然后从协程或其他挂起函数对其进行调用。

以下代码定义了一个名为 UserDao 的 DAO。UserDao 提供了应用的其余部分用于与 user 表中的数据交互的方法。

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    fun getAll(): List<User>

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    fun loadAllByIds(userIds: IntArray): List<User>

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
           "last_name LIKE :last ORDER BY last_name DESC LIMIT 1")
    fun findByName(first: String, last: String): User

    // onConflict 配置主键冲突处理
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertUsers(vararg users: User)

    @Insert
    fun insertBothUsers(user1: User, user2: User)

    @Insert
    fun insertUsersAndFriends(user: User, friends: List<User>)

    @Delete
    fun delete(user: User)
}
  • 如果 @Insert 方法接收单个参数,则会返回 long 值,这是插入项的新 rowId。如果参数是数组或集合,则该方法应改为返回由 long 值组成的数组或集合,并且每个值都作为其中一个插入项的 rowId
  • @Update@Delete 方法可以选择性地返回 int 值,该值指示成功的行数。
  • onConflict配置:ABORT:在发生冲突时回滚事务、IGNORE:保留现有行、REPLACE:替换现有行
  • 查看更多sqlite语法
interface BaseDAO<T> {

    @Insert
    suspend fun insert(obj: T)

    @Insert
    suspend fun insert(list: List<T>)

    @Update
    suspend fun update(obj: T)

    @Update
    suspend fun update(list: List<T>)

    @Delete
    suspend fun delete(obj: T)

    @Delete
    suspend fun delete(list: List<T>)
}

除了直接在DAO中写方法,还可以创建DAO的基类,把基础方法提取出来,减少重复代码。

配置数据库

Room 数据库类必须是抽象且必须扩展 RoomDatabase。整个应用通常只需要一个 Room 数据库实例。数据库类必须满足以下条件:

  • 该类必须带有 @Database 注解,该注解包含列出所有与数据库关联的数据实体的 entities 数组。
  • 该类必须是一个抽象类,用于扩展 RoomDatabase
  • 对于与数据库关联的每个 DAO 类,必须在数据库类中定义一个具有零参数的抽象方法,并返回 DAO 类的实例。
@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {

   abstract fun userDao(): UserDao

   companion object {
        // 防止同一时间创建多个实例
        @Volatile
        private var INSTANCE: AppDatabase ? = null

        fun getDatabase(context: Context): AppDatabase {
            // 单例
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                        // 可以使用单例Application来代替此参数传递
                        context.applicationContext,
                        AppDatabase::class.java,
                        "database_name"
                    ).build()
                INSTANCE = instance

                instance
            }
        }
   }
}

现在数据库已经可以正常使用了,可以通过以下方式操作数据库:

AppDatabase.getDatabase(context).userDao().insertUsers(user)

保存Date等复杂类型数据

Date等类型字段,Room是不知道怎么存的,需要通过转换器来保存和读取。

class Converters {
    @TypeConverter
    fun fromTimestamp(value: Long?): Date? {
        return value?.let { Date(it) }
    }

    @TypeConverter
    fun dateToTimestamp(date: Date?): Long? {
        return date?.time?.toLong()
    }
}

在数据库类配置注解。

@Database(entities = [User::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase()

接下来直接在实体声明Date类型的参数即可,当写的时候会调用dateToTimestamp,读的时候会调用fromTimestamp。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,233评论 6 495
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,357评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,831评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,313评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,417评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,470评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,482评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,265评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,708评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,997评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,176评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,503评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,150评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,391评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,034评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,063评论 2 352

推荐阅读更多精彩内容