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