Jetpack架构组件库:Room

本文详述了Android Jetpack组件中的Room数据库的使用,包括添加依赖、定义Entity、创建DAO、设置冲突策略、观察数据库变化、创建数据仓库、使用ViewModel及数据升级等。Room是基于SQLite的抽象层,通过注解简化数据库操作,提供编译时验证,支持LiveData和协程,便于在Android应用中处理数据。

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

Room

Room是一款轻量级orm数据库,本质上是一个基于SQLite之上的抽象层。它通过注解的方式提供相关功能,编译时自动生成实现Impl,相比纯 SQLite 的API使用方式更加简单。另外一个相比于SQLite API的优势是:它会在编译时检查 SQL 语句的合法性,而不是等到运行时应用崩溃才发现。

Room 的使用

添加依赖项

dependencies {
   
    def room_version = "2.5.0"

    implementation "androidx.room:room-runtime:$room_version"
    implementation "androidx.room:room-ktx:$room_version"
    
    // annotationProcessor "androidx.room:room-compiler:$room_version"
    
    // To use Kotlin annotation processing tool (kapt)
    // kapt "androidx.room:room-compiler:$room_version"
    
    // To use Kotlin Symbol Processing (KSP)
    ksp "androidx.room:room-compiler:$room_version"
}

注解处理器这里建议使用 KSP,编译速度更快。

定义Entity实体

@Entity
data class User(@PrimaryKey val id: Int, val name: String, val age: Int) 

默认表名与类名相同,如需显示指定表名,使用 @Entity(tableName = "user_table")

@Entity(tableName = "user_table")
data class User(@PrimaryKey val id: Int, val name: String, val age: Int) 

如需显示指定表中的列名,使用 @ColumnInfo(name = "xxx")

@Entity
data class User(
    @PrimaryKey 
    val id: Int,
    @ColumnInfo(name = "userName") 
    val name: String,
    val age: Int,
) 

定义主键

每个 Room 实体都必须定义一个主键,用于唯一标识相应数据库表中的每一行。执行此操作的最直接方式是使用 @PrimaryKey 为单个列添加注解:

@Entity
data class User(
    @PrimaryKey // 主键
    val id: Int, 
    val name: String,
    val age: Int
) 

如需设置主键自动生成,使用 @PrimaryKey(autoGenerate = true)

@Entity
data class User(
    @PrimaryKey(autoGenerate = true) // 设置主键自动生成
    val id: Int, 
    val name: String,
    val age: Int,
)

如需定义复合主键进行唯一标识,使用 @Entity(primaryKeys = ["name1", "name2"])

@Entity(primaryKeys = ["firstName", "lastName"])
data class User(
    val firstName: String?,
    val lastName: String?
)

忽略字段

默认情况下,Room 会为实体中定义的每个字段创建一个列。 如果某个实体中有不想保留的字段,则可以使用 @Ignore 为这些字段添加注解:

@Entity
data class User(
    @PrimaryKey val id: Int,
    val firstName: String?,
    val lastName: String?,
    @Ignore val picture: Bitmap?
)

如果实体继承了父实体的字段,则使用 @Entity 属性的 ignoredColumns 属性通常会更容易:

open class User {
   
    var picture: Bitmap? = null
}

@Entity(ignoredColumns = ["picture"])
data class RemoteUser(
    @PrimaryKey val id: Int,
    val hasVpn: Boolean
) : User()

创建嵌套对象

例如,User 类可以包含一个 Address 类型的字段,它表示名为 street、city、state 和 postCode 的字段的组合。若要在表中单独存储组合列,请在 User 类中添加 Address 字段,并添加 @Embedded 注解,如以下代码段所示:

data class Address(
    val street: String?,
    val state: String?,
    val city: String?,
    @ColumnInfo(name = "post_code") val postCode: Int
)

@Entity
data class User(
    @PrimaryKey val id: Int,
    val firstName: String?,
    @Embedded val address: Address?
)

然后,表示 User 对象的表将包含具有以下名称的列:id、firstName、street、state、city 和 post_code。

注意:嵌套字段还可以包含其他嵌套字段。如果某个实体具有相同类型的多个嵌套字段,可以通过设置 prefix 属性确保每个列的唯一性。然后,Room 会将提供的值添加到嵌套对象中每个列名称的开头。

支持全文搜索

如果应用需要通过全文搜索 (FTS) 快速访问数据库信息,请使用虚拟表(使用 FTS3 或 FTS4 SQLite 扩展模块)为实体提供支持。如需使用 Room 2.1.0 及更高版本中提供的这项功能,请将 @Fts3@Fts4 注解添加到给定实体,如下代码所示:

// 只有当你的应用程序对磁盘空间有严格的要求,或者你需要与旧SQLite版本兼容时才使用“@Fts3”
@Fts4
@Entity(tableName = "users")
data class User(
    /* 为FTS表支持的实体指定主键是可选的,但是如果指定了,则必须使用Int类型和rowid列名 */
    @PrimaryKey @ColumnInfo(name = "rowid") val id: Int,
    @ColumnInfo(name = "first_name") val firstName: String?
)

注意:启用 FTS 的表始终使用 INTEGER 类型的主键且列名称为“rowid”。如果是由 FTS 表支持的实体定义主键,则必须使用相应的类型和列名称。

如果表支持以多种语言显示的内容,请使用 languageId 选项指定用于存储每一行语言信息的列:

@Fts4(languageId = "lid")
@Entity(tableName = "users")
data class User(
    // ...
    @ColumnInfo(name = "lid") val languageId: Int
)

为特定列添加索引

如果您的应用必须支持不允许使用由 FTS3 或 FTS4 表支持的实体的 SDK 版本,您仍可以将数据库中的某些列编入索引,以加快查询速度。如需为实体添加索引,请在 @Entity 注解中添加 indices 属性,列出要在索引或复合索引中包含的列的名称。

@Entity(indices = [Index(value = ["last_name", "address"])])
data class User(
    @PrimaryKey val id: Int,
    val firstName: String?,
    val address: String?,
    @ColumnInfo(name = "last_name") val lastName: String?,
    @Ignore val picture: Bitmap?
)

添加基于 AutoValue 的对象

Room 2.1.0 及更高版本中,您可以将基于 Java不可变值类(使用 @AutoValue 进行注解)用作应用数据库中的实体。此支持在实体的两个实例被视为相等(如果这两个实例的列包含相同的值)时尤为有用。

将带有 @AutoValue 注解的类用作实体时,您可以使用 @PrimaryKey、@ColumnInfo、@Embedded@Relation 为该类的抽象方法添加注解。但是,您必须在每次使用这些注解时添加 @CopyAnnotations 注解,以便 Room 可以正确解释这些方法的自动生成实现。

以下代码段展示了一个使用 @AutoValue 进行注解的类(Room 将其标识为实体)的示例:

// User.java
@AutoValue
@Entity
public abstract class User {
   
    // Supported annotations must include `@CopyAnnotations`.
    @CopyAnnotations
    @PrimaryKey
    public abstract long getId();

    public abstract String getFirstName();
    public abstract String getLastName();

    // Room uses this factory method to create User objects.
    public static User create(long id, String firstName, String lastName) {
   
        return new AutoValue_User(id, firstName, lastName);
    }
}

注意:此功能旨在用于基于 Java 的实体。如需在基于 Kotlin 的实体中实现相同的功能,最好改用数据类

创建 DAO

什么是 DAO?

在 DAO(Database Access Object 数据访问对象)中,您可以指定 SQL 查询并将其与方法调用相关联。编译器会检查 SQL 并根据常见查询的方便的注解(如 @Insert)生成查询。Room 会使用 DAO 为代码创建整洁的 API。

  • DAO 必须是一个接口或抽象类
  • 默认情况下,所有查询都必须在单独的线程上执行。
  • Room 支持 Kotlin 协程,您可使用 suspend 修饰符对查询进行注解,然后从协程或其他挂起函数对其进行调用。
@Dao 
interface UserDao {
   

    @Insert 
    suspend fun insert(vararg user: User) // 注意是挂起函数

    @Update
    suspend fun update(vararg user: User)

    @Delete
    suspend fun delete(vararg user: User)  

    @Query("DELETE FROM user") // 表名会自动转大写
    suspend fun deleteAll()  

    @Query("SELECT * FROM user")
    fun getAllUser(): List<User>
}

插入

@Insert 方法的每个参数必须是带有 @Entity 注解的 Room 数据实体类的实例或数据实体类实例的集合。调用 @Insert 方法时,Room 会将每个传递的实体实例插入到相应的数据库表中。

@Dao  
interface UserDao {
   
    @Insert 
    suspend fun insert(vararg user: User) 
    
    @Insert
    fun insertBothUsers(user1: User, user2: User)
    
    @Insert
    fun insertUsersAndFriends(user: User, friends: List<User>)
}

如果 @Insert 方法接收单个参数,则会返回 long 值,这是插入项的新 rowId。如果参数是数组或集合,则该方法应改为返回由 long 值组成的数组或集合,并且每个值都作为其中一个插入项的 rowId

更新

@Insert 方法类似,@Update 方法接受数据实体实例作为参数。

@Dao
interface UserDao {
   
    @Update
    fun updateUsers(vararg users: User)
}

Room 使用主键将传递的实体实例与数据库中的行进行匹配。如果没有具有相同主键的行,Room 不会进行任何更改。

@Update 方法可以选择性地返回 int 值,该值指示成功更新的行数。

删除

@Insert 方法类似,@Delete 方法接受数据实体实例作为参数。

@Dao
interface UserDao {
   
    @Delete
    fun deleteUsers(vararg users: User)
}

Room 使用主键将传递的实体实例与数据库中的行进行匹配。如果没有具有相同主键的行,Room 不会进行任何更改。

@Delete 方法可以选择性地返回 int 值,该值指示成功删除的行数。

查询

使用 @Query 注解,您可以编写 SQL 语句并将其作为 DAO 方法公开。使用这些查询方法从应用的数据库查询数据,或者需要执行更复杂的插入、更新和删除操作。

Room 会在编译时验证 SQL 查询。这意味着,如果查询出现问题,则会出现编译错误,而不是运行时失败。

简单查询

以下代码定义了一个方法,该方法使用简单的 SELECT 查询返回数据库中的所有 User 对象:

@Query("SELECT * FROM user")
fun loadAllUsers(): Array<User>
查询指定的列

在大多数情况下,您只需要返回要查询的表中的列的子集。为节省资源并简化查询的执行,您应只查询所需的字段。

借助 Room,您可以从任何查询返回简单对象,前提是您可以将一组结果列映射到返回的对象。例如,您可以定义以下对象来保存用户的名字和姓氏:

data class NameTuple(
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值