1、数据依赖
dependencies {
val room_version = "2.5.0"
implementation("androidx.room:room-runtime:$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")
// optional - Kotlin Extensions and Coroutines support for Room
implementation("androidx.room:room-ktx:$room_version")
// optional - RxJava2 support for Room
implementation("androidx.room:room-rxjava2:$room_version")
// optional - RxJava3 support for Room
implementation("androidx.room:room-rxjava3:$room_version")
// optional - Guava support for Room, including Optional and ListenableFuture
implementation("androidx.room:room-guava:$room_version")
// optional - Test helpers
testImplementation("androidx.room:room-testing:$room_version")
// optional - Paging 3 Integration
implementation("androidx.room:room-paging:$room_version")
}
2、主要组件

组件结构
- 数据库类 用于保存数据库并作为应用持久性数据底层连接的主要访问点。
- 数据实体 用于表示应用的数据库中的表。
- 数据访问对象 (DAO) 提供您的应用可用于查询、更新、插入和删除数据库中的数据的方法。
3、数据库的创建
- 如果只是获取数据库的实体或者获取 dao 的时候不会创建数据库
- 只有进行数据操作的时候才会创建数据库
4、使用 Room 实体定义数据
4.1、实体详解
- @Entity 注解的类
- @Entity(tableName = "users") 注解的类并指定表名
- @ColumnInfo(name = "first_name") val firstName: String? 指定列名
4.2、定义主键
- @PrimaryKey val id: Int
- @Entity(primaryKeys = ["firstName", "lastName"]) 联合主键
@Entity(primaryKeys = ["firstName", "lastName"])
data class User(
val firstName: String?,
val lastName: String?
)
4.3、忽略字段
- @Ignore val picture: Bitmap?
- 继承忽略字段
open class User {
var picture: Bitmap? = null
}
@Entity(ignoredColumns = ["picture"])
data class RemoteUser(
@PrimaryKey val id: Int,
val hasVpn: Boolean
) : User()
4.4 提供表搜索支持
- 支持全文搜索 FTS
/**
* @author 支豪亮
* @date 2023/1/30 14:42
* @des 支持全文搜索
*
* 全文搜索是从文本或数据库中,不限定数据字段自由地搜索出信息的技术。
* 它的工作原理是扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当进行查询时,
* 数据库就根据事先建立的索引进行查找,从而大幅度加快查找速度
*
* 当时文件的插入速度会变慢
* 支持 MATCH 语句
* 数据量不大时 like 语句更快
*/
@Fts4
@Entity(tableName = "users_fts")
data class UserFts(
/**
* Specifying a primary key for an FTS-table-backed entity is optional
* but if you include one, it must use this type and column name.
*
* FTS 的实体类主键是可选的,如果设置了它的列名需要是 rowid
*
* 如果设置了 rowid 搜索时需要
* "SELECT * ,`rowid`FROM users_fts WHERE last_name MATCH '1*'"
*/
@PrimaryKey val rowid: Int,
@ColumnInfo(name = "last_name") val lastName: String?,
@ColumnInfo(name = "first_name") val firstName: String?,
)
- 将特定列编入索引
/**
* @author 支豪亮
* @date 2023/1/31 09:18
* @des 将特定列编入索引
* 建立索引的目的是加快对表中记录的查找或排序。
*
* 为表设置索引要付出代价的:
* 一是增加了数据库的存储空间,
* 二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)。
* 数据库索引就是为了提高表的搜索效率而对某些字段中的值建立的目录 。
*
* 注解的 unique 属性设为 true,强制实施此唯一性属性
*/
@Entity(tableName = "users_index" ,indices = [Index(value = ["first_name", "last_name"],
unique = true)])
data class UserIndex(
@PrimaryKey val id: Int,
@ColumnInfo(name = "last_name") val lastName: String?,
@ColumnInfo(name = "first_name") val firstName: String?,
)
5、使用 Room DAO 访问数据
5.1、便捷方法
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUsers(vararg users: User)
@Insert
fun insertBothUsers(user1: User, user2: User)
@Insert
fun insertUsersAndFriends(user: User, friends: List<User>)
@Update
fun updateUsers(vararg users: User)
@Delete
fun deleteUsers(vararg users: User)
}
- 插入
- onConflict = OnConflictStrategy.REPLACE 有相同的值则会覆盖,否则会返回异常
- 返回值
- 如果 @Insert 方法接收单个参数,则会返回 long 值,这是插入项的新 rowId。
- 如果参数是数组或集合,则该方法应改为返回由 long 值组成的数组或集合
- 返回值
- onConflict = OnConflictStrategy.REPLACE 有相同的值则会覆盖,否则会返回异常
- 更新
- Room 使用主键将传递的实体实例与数据库中的行进行匹配。如果没有具有相同主键的行,Room 不会进行任何更改。
- @Update 方法可以选择性地返回 int 值,该值指示成功更新的行数11
- 删除
- Room 使用主键将传递的实体实例与数据库中的行进行匹配。如果没有具有相同主键的行,Room 不会进行任何更改
- @Delete 方法可以选择性地返回 int 值,该值指示成功删除的行数
5.2、简单查询
/**
* 简单的查询
*/
@Query("SELECT * FROM user")
fun loadAllUsers(): Array<User>
/**
* 返回表格列的子集
*/
@Query("SELECT first_name, last_name FROM user")
fun loadFullName(): List<NameTuple>
/**
* 返回表格列的子集
*/
@Query("SELECT first_name, last_name FROM user")
fun loadFullName(): List<NameTuple>
/**
* 将简单参数传递给查询
*/
@Query("SELECT * FROM user WHERE age > :minAge")
fun loadAllUsersOlderThan(minAge: Int): Array<User>
/**
* 将简单参数传递给查询
*/
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User>
/**
* 将简单参数传递给查询
*/
@Query("SELECT * FROM user WHERE first_name LIKE :search " +
"OR last_name LIKE :search")
fun findUserWithName(search: String): List<User>
/**
* 将一组参数传递给查询
*/
@Query("SELECT * FROM user WHERE region IN (:regions)")
fun loadUsersFromRegions(regions: List<String>): List<User>
/**
* 查询多个表
*/
@Query(
"SELECT * FROM book " +
"INNER JOIN loan ON loan.book_id = book.id " +
"INNER JOIN user ON user.id = loan.user_id " +
"WHERE user.name LIKE :userName"
)
fun findBooksBorrowedByNameSync(userName: String): List<Book>
/**
* 查询多个表 并返回子集
*/
@Query(
"SELECT user.name AS userName, book.name AS bookName " +
"FROM user, book " +
"WHERE user.id = book.user_id"
)
fun loadUserAndBookNames(): LiveData<List<UserBook>>
// You can also define this class in a separate file.
data class UserBook(val userName: String?, val bookName: String?)
/**
* 返回多重映射
*/
@Query(
"SELECT * FROM user" +
"JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<User, List<Book>>
/**
* 返回多重映射
*/
@Query(
"SELECT * FROM user" +
"JOIN book ON user.id = book.user_id" +
"GROUP BY user.name WHERE COUNT(book.id) >= 3"
)
fun loadUserAndBookNames(): Map<User, List<Book>>
/**
* 返回多重映射
*/
@MapInfo(keyColumn = "userName", valueColumn = "bookName")
@Query(
"SELECT user.name AS username, book.name AS bookname FROM user" +
"JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<String, List<String>>
/**
* 使用 Paging 库将查询分页
*/
@Query("SELECT * FROM users WHERE label LIKE :query")
fun pagingSource(query: String): PagingSource<Int, User>
/**
* 直接光标访问
*/
@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
fun loadRawUsersOlderThan(minAge: Int): Cursor
6、定义对象之间的关系
6.1、中间数据类 vs 多重映射返回值类型
@Dao
interface UserBookDao {
/**
* 中间数据类
*/
@Query(
"SELECT user.name AS userName, book.name AS bookName " +
"FROM user, book " +
"WHERE user.id = book.user_id"
)
fun loadUserAndBookNames(): LiveData<List<UserBook>>
}
data class UserBook(val userName: String?, val bookName: String?)
/**
* 多重映射返回值类型
* Room 在 2.4 及更高版本中仅支持多重映射返回值类型
*/
@Query(
"SELECT * FROM user" +
"JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<User, List<Book>>
- 中间数据类方法
- 避免编写复杂的 SQL 查询
- 需要额外的数据类,它还可能会导致代码复杂性增加
- 多重映射返回值类型方法
- SQL 查询完成更多工作
6.2、创建嵌套对象
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(prefix = "loc_") 确保每个列的唯一性
@Embedded val address: Address?
)
- 使用 @Embedded 注释表示要分解为表格中的子字段的对象
- 设置 prefix 属性确保每个列的唯一性
- user 表包含了 street、state 等列
6.3、一对一关系
@Entity(tableName = "user_library")
data class UserLibrary(
@PrimaryKey val userId: Long,
val name: String,
val age: Int
)
@Entity
data class Library(
@PrimaryKey val libraryId: Long,
val userOwnerId: Long,
val name:String
)
data class UserAndLibrary(
@Embedded val user: UserLibrary,
@Relation(
parentColumn = "userId",
entityColumn = "userOwnerId"
)
val library: Library
)
@Dao
interface UserLibraryDao{
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(userLibrary: List<UserLibrary>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertLibrary(library: List<Library>)
//需要 Room 运行两次查询,因此应向该方法添加 @Transaction 注释,以确保整个操作以原子方式执行
@Transaction
@Query("SELECT * FROM user_library")
suspend fun queryUsersAndLibraries(): List<UserAndLibrary>
}