Android Room 入坑详解

作者: 枫未晚 | 来源:发表于2022-04-26 09:27 被阅读0次

    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。

    相关文章

      网友评论

        本文标题:Android Room 入坑详解

        本文链接:https://www.haomeiwen.com/subject/fbaaertx.html