android ROOM kotlin官方文档完全学习2.6
使用 Room 将数据保存到本地数据库 | Android Developers (google.cn)
一、简介
1.1 引入
dependencies {
def room_version = "2.6.1"
implementation "androidx.room:room-runtime:$room_version"
//如下三选一
annotationProcessor "androidx.room:room-compiler:$room_version"
kapt "androidx.room:room-compiler:$room_version"
ksp "androidx.room:room-compiler:$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"
}
1.2 三大组件
- DataBase,用于保存数据库并作为应用持久性数据底层连接的主要访问点。
- Entity,用于表示应用的数据库中的表。
- DAO(数据访问对象),为您的应用提供在数据库中查询、更新、插入和删除数据的方法。
1.3 快速示例
1.3.1 定义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?
)
详细使用参考第二章【使用Room实体定义数据】。
1.3.2 数据访问对象(DAO)
用来操控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 LIMIT 1")
fun findByName(first: String, last: String): User
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
详细使用参考第三章【使用DAO访问数据】。
1.3.3 数据库
作为应用对持久性数据的主要访问点。几个条件如下:
使用@Database注解,列出所有的entities。
必须抽象类,继承自RoomDatabase。
每一个DAO类,都需要一个定义一个零参数的抽象函数,返回DAO类的实例。
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private var instance:AppDatabase? = null
private const val DATA_BASE_NAME = "app"
private val lock = Any()
/**
* 调用单例数据库
*/
val db : AppDatabase
get() {
if (instance == null) {
synchronized(lock) {
if (instance == null) {
instance = Room.databaseBuilder(
Globals.app,
AppDatabase::class.java,
DATA_BASE_NAME
)
//.enableMultiInstanceInvalidation() 多进程启用 todo
.build()
}
}
}
return instance!!
}
}
}
//使用的方法:
val userDao = AppDatabase.db().userDao()
val users: List<User> = userDao.getAll()
二、使用 Room 实体定义数据
2.1 实体详解(@Entity, tableName)
@Entity(tableName = "users")
data class User(
@PrimaryKey val id: Int,
val firstName: String?,
val lastName: String?
)
必须是public或者有get,set函数。定义为每一列。
类名就是就是数据表名,可以通过@entity里面的tableName修改。
字段名可以通过ColumnInfo的name修改。
表和列名都不区分大小写。
2.2 主键(@PrimaryKey, autoGenerate)
必须定义主键,并使用@PrimaryKey注解。
@PrimaryKey val id: Int
由于默认是@PrimaryKey(autoGenerate = false)是false,所以必须自行管理id的唯一性,所以一般情况,我们需要设置为true。
2.3 复合主键(@Entity primaryKeys)
@Entity(primaryKeys = ["firstName", "lastName"])
data class User(
val firstName: String?,
val lastName: String?
)
如果您需要通过多个列的组合对实体实例进行唯一标识,则可以通过列出@Entity primaryKeys属性中的以下列定义一个复合主键。
2.4 忽略字段(@Ignore,@Entity ignoredColumns)
@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()
三、使用DAO访问数据
可以定义为接口或者抽象类。使用@Dao进行注解。示例:
@Dao
interface UserDao {
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
@Query("SELECT * FROM user")
fun getAll(): List<User>
}
有2种方式定义函数:
- 3.1 不用编写SQL代码,实现
插入
,更新
,删除
数据库的行。 - 3.2 自行编写SQL
查询
。
3.1 便捷方法
Insert
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) //遇到有数据就替换,可以选择ABORT,IGNORE
fun insertUsers(vararg users: User)
@Insert
fun insertBothUsers(user1: User, user2: User)
@Insert
fun insertUsersAndFriends(user: User, friends: List<User>)
}
自己可以追加返回值,单入参就返回long型的rowId。多入参就定义返回值,数组型的rowId。TODO验证是需要定义还是自行追加。
Update
@Dao
interface UserDao {
@Update
fun updateUsers(vararg users: User)
}
可以追加返回值,指示成功更新的行数。TODO验证是否会优先插入。
现在Insert和Update都只有三个OnConflictStrategy可选,REPLACE,IGNORE和ABORT。
Delete
@Dao
interface UserDao {
@Delete
fun deleteUsers(vararg users: User)
}
可以删除1个或者多个。会根据主键去删除,如果没有相同主键的行,就不会有任何改变。可以追加返回值,返回的是int的删除行数。
3.2 复杂方法(Query)
不要认为query就是查询了。@Query注解,可以自定义SQL语句。有个优点是编译就会报错来提示你。
简单查询
@Query("SELECT * FROM user")
fun loadAllUsers(): Array<User>
查询所有User对象。
返回表中多列的子集
- 通过定义一个简单的类:
data class NameTuple(
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
- 定义DAO函数:
@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>
3.5 返回Cursor
@Dao
interface UserDao {
@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
fun loadRawUsersOlderThan(minAge: Int): Cursor
}
不推荐,因为它无法保证行是否存在或行包含哪些值。
四、定义对象之间的关系
4.1 两张表之间的混合查询
在 Room 中,您可以通过两种方式定义和查询实体之间的关系:
使用具有嵌入式对象的中间数据类。
使用具有多重映射返回值类型的关系型查询方法。
中间数据类
在中间数据类方法中,您可以定义数据类,以便在 Room 实体之间建立关系。此数据类保存一个实体的实例与另一个实体的实例之间的配对(作为嵌入式对象)。然后,查询方法可以返回此数据类的实例,以供您的应用使用。
例如,您可以定义 UserBook
数据类来表示已借阅特定图书的图书馆用户,并定义一个查询方法用于从数据库中检索 UserBook
实例的列表:
@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?)
返回Map(推荐)
room2.4支持如下操作:
@Query(
"SELECT * FROM user" +
"JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<User, List<Book>>
4.2 创建嵌套对象(@Embeded)
有时,您可能希望在数据库逻辑中将某个实体或数据对象表示为一个紧密的整体,即使该对象包含多个字段也是如此。在这些情况下,您可以使用 @Embedded
注释表示要分解为表格中的子字段的对象。然后,您可以像查询其他各个列一样查询嵌套字段。
例如,您的 User
类可以包含一个 Address
类型的字段,它表示名为 street
、city
、state
和 postCode
的字段的组合。若要在表中单独存储组合列,请在带有 @Embedded
注解的 User
类中添加 Address
字段,如以下代码段所示:
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
。
七、更高级的用法
暂时不去学习,备查。查看官网。
2.5 表搜索支持
2.6 autoValue,java不可变值类
3.3 查询多张表
您的部分查询可能需要访问多个表格才能计算出结果。您可以在 SQL 查询中使用 JOIN
子句来引用多个表。
以下代码定义了一种方法将三个表进行联接,以便返回当前已出借给特定用户的图书:
@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>
进阶
此外,您还可以定义简单对象以从多个联接表返回若干列的子集,如【返回表中多列的子集】部分所述。以下代码定义了一个 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>>
// You can also define this class in a separate file.
data class UserBook(val userName: String?, val bookName: String?)
}
进阶2-返回Map
//您可以直接从您的查询方法返回 `User` 和 `Book` 的映射,而不是返回保存有 `User` 和 `Book` 实例配对的自定义数据类的实例列表。
@Query(
"SELECT * FROM user" +
"JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<User, List<Book>>
//GROUP BY以便利用 SQL 的功能进行高级计算和过滤。
@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 和 valueColumn 属性,返回查询中特定列之间的映射:
@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>>