赞
踩
1. Room介绍,直接Copy官网介绍:
Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优势:
总结就是简化了操作数据库的难度,相对于直接操作Sqlite Api。
2. Room主要组件,包含如下三个部分,内容直接Copy官网:
如果链接访问不了,直接将前缀修改为中文官网的域名(https://developer.android.google.cn/)
引入依赖库,参考Android版本依赖Version catalog
- // Room
- implementation(libs.room.runtime)
- annotationProcessor(libs.room.compiler)
- // To use Kotlin Symbol Processing (KSP)
- ksp(libs.room.compiler)
- // optional - Kotlin Extensions and Coroutines support for Room
- implementation(libs.room.ktx)
- // 测试数据库
- testImplementation(libs.room.testing)
每个 Room 实体定义为带有 @Entity 注解的类。Room 实体包含数据库中相应表中的每一列的字段,包括构成主键的一个或多个列。
- /**
- * 用户表对象.
- *
- * @param id
- * 如果您在定义实体类时将一个字段标记为 @PrimaryKey(autoGenerate = true),
- * 并且在插入记录时不为该字段赋值,Room 将会自动生成一个唯一的值并填充到该字段中。
- */
- @Entity
- class User(
- /**
- * 主键字段不能为空,并且必须具有唯一性,以确保每条记录都可以唯一标识.
- *
- * autoGenerate = true之后我们将主键默认设置为空或者0,
- * room也会自动自动生成一个唯一的值并填充到该字段中,
- * 但是不建议设置为空,建议默认设置为0。
- */
- @PrimaryKey(autoGenerate = true)
- val id: Long = 0,
- val name: String,
- val age: Int = 0,
- @GenderType
- val gender: Int = GenderType.MALE
- ) {
- override fun toString(): String {
- return "User(id=$id, name='$name', age=$age, gender=$gender)"
- }
- }

注意:
1. 要保留某个字段,Room 必须拥有该字段的访问权限。
2. 可以通过将某个字段设为公开或为其提供 getter 和 setter 方法,确保 Room 能够访问该字段。
@Entity
注解的 tableName 属性。- @Entity(tableName = "users")
- class User(
- @PrimaryKey(autoGenerate = true)
- val id: Long = 0,
- @ColumnInfo(name = "uaerName")
- val name: String,
- val age: Int = 0,
- @GenderType
- val gender: Int = GenderType.MALE
- )
注意:SQLite 中的表和列名称不区分大小写。
在 Room 数据库中,主键通常定义为长整型(long)是因为长整型提供了更大的范围,可以容纳更多的唯一标识符。这种做法是出于以下几个考虑:
唯一标识符范围:长整型可以表示更大范围的整数值,最大可达到 2^63 - 1,相比于整型(int)的最大值 2^31 - 1,长整型提供了更大的唯一标识符范围,可以满足大多数情况下的需求。
避免溢出:使用长整型可以避免标识符溢出的问题。长整型的范围比较大,即使在较长时间内也不太可能出现溢出的情况。
与数据库兼容性:在许多数据库系统中,主键通常被定义为长整型或者类似的数据类型(如 BIGINT),这样可以确保 Room 数据库与其他数据库系统的兼容性。
- @PrimaryKey(autoGenerate = true)
- val id: Long = 0
1. 如果您在定义实体类时将一个字段标记为 @PrimaryKey(autoGenerate = true),
并且在插入记录时不为该字段赋值,Room 将会自动生成一个唯一的值并填充到该字段中。
2. 主键字段不能为空,并且必须具有唯一性,以确保每条记录都可以唯一标识.
3. autoGenerate = true之后我们将主键默认设置为空或者0,room也会自动自动生成一个唯一的值并填充到该字段中,但是不建议设置为空,建议默认设置为0。
默认Room 会为实体中定义的每个字段创建一个列。 如果某个实体中有不想保留的字段,则可以使用 @Ignore 为这些字段添加注解
- @Entity
- class User(
- @PrimaryKey(autoGenerate = true)
- val id: Long = 0,
- val name: String,
- val age: Int = 0,
- @GenderType
- val gender: Int = GenderType.MALE,
- @Ignore val headBm: Bitmap?
- )
将每个 DAO 定义为一个接口或一个抽象类。通常应使用接口。
无论哪种形式,都必须始终使用 @Dao 为创建 DAO 对象添加注解。DAO 不具有属性,但它们定义了一个或多个方法,可用于与应用数据库中的数据进行交互。
两种类型的 DAO 方法可以定义数据库交互:
1. 对象必须是接口或者抽象类
2. 对象必须使用@DAO注解进行修饰
3. 增、删、改不需要使用任何SQL语句,直接使用@Insert、@Delete、@Update注解方法即可
4. 查询方法直接编写SQL语句就能使用,例如:@Query("SELECT *FROM user")
使用 @Insert 注解,您可以定义将其参数插入数据库中的相应表的方法,可以同时插入一个或多个对象。
@Entity数据库表,例如例子中的User表
@Insert
注解的方法每个参数都必须是一个带有 @Entity
注解的 Room 数据实体类实例。@Insert
方法接收到单个参数,则可返回一个 long
值,这是插入项的新 rowId
。@Entity数据库表集合或者数组
@Insert
方法时,Room 会将每个传递的实体实例插入到相应的数据库表中。long
值组成的数组或集合,并且每个值都作为其中一个插入项的 rowId
。想了解如何返回 rowId
值,请参阅 @Insert 注解的参考文档以及 rowid 表的 SQLite 文档。
- /**
- * 插入多个用户数据(碰到冲突就替换为最新的).
- *
- * @param users 用户数据(可变对象)
- * @return 插入数据ID集合
- */
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- fun insertAll(vararg users: User): List<Long>
-
- /**
- * 插入用户集合数据.
- *
- * @param users 用户数据集合
- * @return 插入数据ID集合
- */
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- fun insertAll(users: List<User>): List<Long>

OnConflictStrategy常量介绍:
NONE
:没有冲突处理策略,事务将会回滚。REPLACE
:替换旧数据并继续事务。ROLLBACK
:事务回滚。已弃用,请使用 ABORT
。ABORT
:事务回滚。FAIL
:事务失败。已弃用,请使用 ABORT
。IGNORE
:忽略冲突。更新方法直接用@Update修饰,ROOM支持更新单个对象(单行)、集合对象(多行)、数组对象(查看源码:EntityDeletionOrUpdateAdapter)。与 @Insert
方法一样,@Update
方法接受数据实体实例作为参数。
- /**
- * 更新用户数据.
- *
- * @param users 用户数据(可变对象)
- * @return 更新数据ID集合
- */
- @Update
- fun update(vararg users: User)
Room 使用主键 (就是User表中的id)将传递的实体实例与数据库中的行进行匹配。如果没有具有相同主键的行,Room 不会进行任何更改。
@Update
方法可以选择性地返回 int
值,指示成功更新的行数。
删除方法直接用@Delete修饰,ROOM支持删除单个对象、集合对象、数组对象(查看源码:EntityDeletionOrUpdateAdapter)
- /**
- * 删除多个用户数据.
- *
- * @param users 用户数据(可变对象)
- */
- @Delete
- fun delete(vararg users: User)
-
- /**
- * 删除用户集合.
- *
- * @param users 用户集合
- */
- @Delete
- fun delete(users: List<User>)
从源码中我们可以看到,ROOM中删除和更新调用的是相同的方法。
Room 使用主键将传递的实体实例与数据库中的行进行匹配。如果没有具有相同主键的行,Room 不会进行任何更改。
@Delete
方法可以选择性地返回 int
值,指示成功删除的行数。
Room的查询主要是写Sql语句,只要是对Sql语句熟悉的,写起来就是得心应手。
如下常用的查询方法,使用如下注解在方法上:@Query("查询的SQL")
- /**
- * 查询所有用户数据.
- *
- * @return 用户数据集合
- */
- @Query("SELECT *FROM user")
- fun getAll(): List<User>
在数据库查询的时候,我们偶尔不需要查询全部的列,例如此例中的用户表我们可能只需要查询用户ID、用户名。
借助 Room可以从任何查询返回简单对象,前提是将一组结果列映射到需要查询的简单的对象。
举例:
1. 创建需要的列的对象
- /**
- * 偶尔我们只是需要查询用户数据的列子集。
- * 借助 Room,可以从任何查询返回简单对象,前提是可以将一组结果列映射到返回的对象。
- */
- data class UserTuple(
- val id: Long,
- val name: String
- )
2. 查询指定子集
- /**
- * 查询用户数据的列子集.
- *
- * @return 返回用户数据列子集
- */
- @Query("SELECT id, name FROM user")
- fun getSimpleAll(): List<UserTuple>
- /**
- * 通过用户ID查询用户数据.
- *
- * @param id 用户ID
- * @return 用户数据
- */
- @Query("SELECT *FROM user WHERE id = :id")
- fun getById(id: Int): User
-
- /**
- * 通过用户名查询用户数据(没啥意义,用户名可以重名,查询的是最近的用户).
- *
- * @param name 用户名
- * @return 用户数据
- */
- @Query("SELECT *FROM user WHERE name LIKE :name LIMIT 1")
- fun findByName(name: String): User
-
- /**
- * 通过用户名查询用户数据集合.
- *
- * @param name 用户名
- * @return 用户数据集合
- */
- @Query("SELECT *FROM user WHERE name LIKE :name ORDER BY id DESC")
- fun findAllByName(name: String): List<User>
-
- /**
- * 查询某一年龄段用户数据.
- *
- * @param minAge 最小年龄
- * @param maxAge 最大年龄
- * @return 用户数据集合
- */
- @Query("SELECT *FROM user WHERE age BETWEEN :minAge AND :maxAge")
- fun getAllBetweenAges(minAge: Int, maxAge: Int): List<User>

- /**
- * 通过用户ID查询用户数据.
- *
- * @param userIds 用户ID集合
- * @return 用户数据集合
- */
- @Query("SELECT * FROM user WHERE id IN (:userIds)")
- fun getAllByIds(userIds: IntArray): List<User>
增、删、改、查完整的DAO对象代码:
- /**
- * 用户表数据库操作对象.
- */
- @Dao
- interface UserDao {
-
- /**
- * 插入多个用户数据(碰到冲突就替换为最新的).
- *
- * @param users 用户数据(可变对象)
- * @return 插入数据ID集合
- */
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- fun insertAll(vararg users: User): List<Long>
-
- /**
- * 插入用户集合数据.
- *
- * @param users 用户数据集合
- * @return 插入数据ID集合
- */
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- fun insertAll(users: List<User>): List<Long>
-
- /**
- * 删除多个用户数据.
- *
- * @param users 用户数据(可变对象)
- */
- @Delete
- fun delete(vararg users: User)
-
- /**
- * 删除用户集合.
- *
- * @param users 用户集合
- */
- @Delete
- fun delete(users: List<User>)
-
- /**
- * 更新用户数据.
- *
- * @param users 用户数据(可变对象)
- * @return 更新数据ID集合
- */
- @Update
- fun update(vararg users: User)
-
- /**
- * 查询所有用户数据.
- *
- * @return 用户数据集合
- */
- @Query("SELECT *FROM user")
- fun getAll(): List<User>
-
- /**
- * 查询用户数据的列子集.
- *
- * @return 返回用户数据列子集
- */
- @Query("SELECT id, name FROM user")
- fun getSimpleAll(): List<UserTuple>
-
- /**
- * 通过用户ID查询用户数据.
- *
- * @param id 用户ID
- * @return 用户数据
- */
- @Query("SELECT *FROM user WHERE id = :id")
- fun getById(id: Int): User
-
- /**
- * 通过用户ID查询用户数据.
- *
- * @param userIds 用户ID集合
- * @return 用户数据集合
- */
- @Query("SELECT * FROM user WHERE id IN (:userIds)")
- fun getAllByIds(userIds: IntArray): List<User>
-
- /**
- * 通过用户名查询用户数据(没啥意义,用户名可以重名,查询的是最近的用户).
- *
- * @param name 用户名
- * @return 用户数据
- */
- @Query("SELECT *FROM user WHERE name LIKE :name LIMIT 1")
- fun findByName(name: String): User
-
- /**
- * 通过用户名查询用户数据集合.
- *
- * @param name 用户名
- * @return 用户数据集合
- */
- @Query("SELECT *FROM user WHERE name LIKE :name ORDER BY id DESC")
- fun findAllByName(name: String): List<User>
-
- /**
- * 查询某一年龄段用户数据.
- *
- * @param minAge 最小年龄
- * @param maxAge 最大年龄
- * @return 用户数据集合
- */
- @Query("SELECT *FROM user WHERE age BETWEEN :minAge AND :maxAge")
- fun getAllBetweenAges(minAge: Int, maxAge: Int): List<User>
-
- }

数据库类必须满足以下条件:
- /**
- * 数据库.
- */
- @Database(entities = [User::class], version = 1)
- abstract class TestDataBase : RoomDatabase() {
- /**
- * 用户表操作类.
- */
- abstract fun userDao(): UserDao
- }
注意:
1. 如果您的应用在单个进程中运行,在实例化
TestDataBase
对象时应遵循单例设计模式。每个RoomDatabase
实例的成本相当高,而您几乎不需要在单个进程中访问多个实例。2. 如果应用在多个进程中运行,请在数据库构建器调用中包含
enableMultiInstanceInvalidation()
。这样,如果您在每个进程中都有一个TestDataBase
实例,可以在一个进程中使共享数据库文件失效,并且这种失效会自动传播到其他进程中TestDataBase
的实例。
- @RunWith(AndroidJUnit4::class)
- class RoomTest {
- companion object {
- private const val TAG = "RoomTest"
- }
-
- private lateinit var mDb: TestDataBase
- private lateinit var mUserDao: UserDao
- private lateinit var mWorkDao: WorkDao
- private lateinit var mUserWorkDao: UserWorkDao
-
- @Before
- fun createDb() {
- val context = ApplicationProvider.getApplicationContext<Context>()
- mDb = Room.databaseBuilder(
- context,
- TestDataBase::class.java,
- "pp-test-database"
- ).build()
- mUserDao = mDb.userDao()
- mWorkDao = mDb.workDao()
- mUserWorkDao = mDb.userWorkDao()
- }
-
- @After
- fun closeDb() {
- mDb.close()
- }
-
- @Test
- fun testInsert() {
- mUserDao.insertAll(
- User(name = "刘亦菲", age = 20, gender = GenderType.FEMALE),
- User(name = "唐嫣", age = 30, gender = GenderType.FEMALE)
- ).run {
- assertEquals(2, this.size)
- mUserDao.getAll().run {
- for ((index, value ) in this.withIndex()) {
- // 更新
- // mUserDao.update(User(id = value.id, name = "修改$index", age = value.age, gender = value.gender))
- // 插入数据
- mWorkDao.insertAll(Work(introduce = "打工人$index", userId = value.id))
- }
-
- // 删除
- // mUserDao.delete(this[0])
- }
-
-
-
- mUserWorkDao.getAllUserWork().run {
- for ((index, value) in this.withIndex()) {
- "index=$index, value=$value".logI(TAG)
- }
- }
- }
- }
- }

Room操作单表已经介绍完成,按照上述操作即可成功使用room操作Sqlite数据库
由于 SQLite 是关系型数据库,因此您可以定义各个实体之间的关系。虽然大多数对象关系映射库都允许实体对象相互引用,但 Room 明确禁止这样做。
Room 中,可以通过两种方式定义和查询实体之间的关系:
创建Work工作类对象
- @Entity(
- foreignKeys = [ForeignKey(
- entity = User::class,
- parentColumns = arrayOf("id"),
- childColumns = arrayOf("userId"),
- onDelete = ForeignKey.CASCADE
- )]
- )
- data class Work(
- @PrimaryKey(autoGenerate = true)
- val id: Long = 0,
- val introduce: String,
- /**
- * 外键,对应用户表的id.
- */
- val userId: Long
- ) {
- override fun toString(): String {
- return "Work(id=$id, introduce='$introduce', userId=$userId)"
- }
- }

在 Room 中,@ForeignKey
注解用于定义外键约束,这些约束用于确保数据的完整性和一致性。使用外键约束可以建立表与表之间的关联关系,从而实现数据的引用完整性。
外键约束的主要作用包括:
维护关联关系:通过外键约束,您可以在一个表中引用另一个表中的数据。这样可以建立表与表之间的关联关系,使得数据之间的关系更加清晰明确。
确保引用完整性:外键约束可以确保在引用另一个表中的数据时,被引用的数据确实存在。这样可以避免出现引用无效数据的情况,保证了数据的完整性。
实现级联操作:通过外键约束,可以实现级联操作,例如在删除或更新父表中的数据时,自动删除或更新子表中相关联的数据。这样可以保持数据之间的一致性。
两个实体之间的一对一关系是指:父实体的每个实例恰好对应于子实体的 1 个实例,反之亦然。
例子:
上述例子中User和Work实体,work表的userId对应的就是User表的id,重新copy一份如下
- @Entity
- class User(
- @PrimaryKey(autoGenerate = true)
- val id: Long = 0,
- val name: String,
- val age: Int = 0,
- @GenderType
- val gender: Int = GenderType.MALE
- )
-
-
- data class Work(
- @PrimaryKey(autoGenerate = true)
- val id: Long = 0,
- val introduce: String,
- /**
- * 外键,对应用户表的id.
- */
- val userId: Long
- )

- /**
- * 用户和对应工作数据.
- *
- * 一对一的关系
- */
- data class UserWork(
- @Embedded
- val user: User,
- @Relation(
- parentColumn = "id",
- entityColumn = "userId"
- )
- val work: Work
- ) {
- override fun toString(): String {
- return "UserWork(user=$user, work=$work)"
- }
- }

向 DAO 类添加一个方法,用于返回将父实体与子实体配对的数据类的所有实例。该方法需要 Room 运行两次查询,因此应向该方法添加 @Transaction 注释,以确保整个操作以原子方式执行。
- /**
- * 用户和对应工作表数据库操作对象.
- */
- @Dao
- interface UserWorkDao {
- /**
- * 查询用户和用户工作数据.
- *
- * @return 用户和用户工作数据集合
- */
- @Transaction
- @Query("SELECT *FROM user")
- fun getAllUserWork(): List<UserWork>
-
- }
父实体的每个实例对应于子实体的零个或多个实例,但子实体的每个实例只能恰好对应于父实体的一个实例。
- /**
- * 用户和对应工作数据.
- *
- * 一对多的关系
- */
- class UserWorkList(
- @Embedded
- val user: User,
- @Relation(
- parentColumn = "id",
- entityColumn = "userId"
- )
- val workList: MutableList<Work>
- ) {
- override fun toString(): String {
- return "UserWorkList(user=$user, workList=$workList)"
- }
- }

向 DAO 类添加一个方法,用于返回将父实体与子实体配对的数据类的所有实例。该方法需要 Room 运行两次查询,因此应向该方法添加 @Transaction 注释,以确保整个操作以原子方式执行。
- /**
- * 用户和对应工作表数据库操作对象.
- */
- @Dao
- interface UserWorkDao {
-
-
- /**
- * 查询用户和用户工作数据.
- *
- * @return 用户和用户工作数据集合
- */
- @Transaction
- @Query("SELECT *FROM user")
- fun getUserWithWorkList(): List<UserWorkList>
- }

两个实体之间的多对多关系是指:父实体的每个实例对应于子实体的零个或多个实例,反之亦然。
例子:
User
实体和 Work
实体之间存在多对多关系。- @Entity
- class User(
- /**
- * 主键字段不能为空,并且必须具有唯一性,以确保每条记录都可以唯一标识.
- *
- * autoGenerate = true之后我们将主键默认设置为空或者0,
- * room也会自动自动生成一个唯一的值并填充到该字段中,
- * 但是不建议设置为空,建议默认设置为0。
- */
- @PrimaryKey(autoGenerate = true)
- val id: Long = 0,
- val name: String,
- val age: Int = 0,
- @GenderType
- val gender: Int = GenderType.MALE
- )
-
- /**
- * 计划表.
- */
- @Entity
- data class Plan(
- @PrimaryKey(autoGenerate = true)
- val planId: Long = 0,
- val planName: String,
- val planTime: Long
- )
-
- /**
- * 用户和对应计划数据.
- *
- * 多对多的关系的交叉引用表
- */
- @Entity(primaryKeys = ["id", "planId"])
- data class UserPlanCrossRef(
- val id: Long,
- val planId: Long
- )

您想如何查询这些相关实体:
User
对象,以及该用户表所包含的所有 Plan
对象的列表。Plan
对象,以及包含该计划的所有 User
对象的列表。在这两种情况下,都可以通过以下方法在实体之间建立关系:在上述每个类中的 @Relation 注释中使用 associateBy 属性来确定提供 User
实体与 Plan
实体之间关系的交叉引用实体。
- /**
- * 用户表对应的计划列表.
- */
- data class UserWithPlans(
- @Embedded val user: User,
- @Relation(
- parentColumn = "id",
- entityColumn = "planId",
- associateBy = Junction(UserPlanCrossRef::class)
- )
- val plans: MutableList<Plan>
- )
-
-
- /**
- * 计划表对应的用户列表.
- */
- data class PlanWithUsers(
- @Embedded val plan: Plan,
- @Relation(
- parentColumn = "planId",
- entityColumn = "id",
- associateBy = Junction(UserPlanCrossRef::class)
- )
- val users: MutableList<User>
- )

向 DAO 类添加一个方法,用于提供您的应用所需的查询功能。
getUserWithPlans
:该方法会查询数据库并返回查询到的所有 UserWithPlans
对象。getPlanWithUsers
:该方法会查询数据库并返回查询到的所有 PlanWithUsers
对象。这两个方法都需要 Room 运行两次查询,因此应为这两个方法添加 @Transaction 注释,以确保整个操作以原子方式执行。
- /**
- * 用户和对应计划表数据库操作对象.
- */
- @Dao
- interface UserPlanDao {
-
- /**
- * 查询用户和对应计划数据.
- *
- * @return 用户和对应计划数据
- */
- @Transaction
- @Query("SELECT *FROM user")
- fun getUserWithPlans(): List<UserWithPlans>
-
- /**
- * 查询计划和对应用户数据.
- *
- * @return 查询计划和对应用户数据
- */
- @Transaction
- @Query("SELECT *FROM `plan`")
- fun getPlanWithUsers(): List<PlanWithUsers>
- }

注意:如果
@Relation
注释不适用于你的特定用例,可能需要在 SQL 查询中使用JOIN
关键字来手动定义适当的关系。如需详细了解如何手动查询多个表,请参阅使用 Room DAO 访问数据。
嵌套关系请直接参考官方文档,一般不建议使用
注意:Room 在 2.4 及更高版本中仅支持多重映射返回值类型。
在多重映射返回值类型方法中,无需定义任何其他数据类,而是根据所需的映射结构为您的方法定义多重映射返回值类型,并直接在 SQL 查询中定义实体之间的关系。
以下查询方法会返回 User
和 Work 实例的映射,用于表示指定用户和用户参与的所有工作
- @Query("SELECT *FROM user JOIN work ON user.id = work.userId")
- fun getUserWithWorks(): Map<User, List<Work>>
@Database(entities = [User::class, Work::class, Plan::class, UserPlanCrossRef::class], version = 1)
- /**
- * 数据库.
- */
- @Database(entities = [User::class, Work::class, Plan::class, UserPlanCrossRef::class], version = 1)
- abstract class TestDataBase : RoomDatabase() {
- /**
- * 用户表操作类.
- */
- abstract fun userDao(): UserDao
-
- /**
- * 工作表操作类.
- */
- abstract fun workDao(): WorkDao
-
- /**
- * 计划表操作类.
- */
- abstract fun planDao(): PlanDao
-
- /**
- * 用户表和工作表操作类.
- */
- abstract fun userWorkDao(): UserWorkDao
-
- /**
- * 用户表和计划表操作类.
- */
- abstract fun userPlanDao(): UserPlanDao
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。