AndroidX Jetpack Room 介绍
Room 持久性库
Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制
该库可帮助您在运行应用的设备上创建应用数据的缓存。此缓存充当应用的单一可信来源,使用户能够在应用中查看关键信息的一致副本,无论用户是否具有互联网连接
引用
在module的build.gradle中添加(Java与kotlin引用方式稍有差异)
//加入room数据库
implementation "androidx.room:room-runtime:2.5.0"
//java使用
annotationProcessor "androidx.room:room-compiler:2.4.2"
//Room 支持RxJava2
implementation 'androidx.room:room-rxjava2:2.5.0'
plugins {
id 'kotlin-kapt'
}
dependencies {
implementation "androidx.room:room-runtime:2.5.0"
implementation "androidx.room:room-ktx:2.5.0"
kapt "androidx.room:room-compiler:2.5.0"
}
Room的组成
-
数据库类
用于保存数据库并作为应用持久性数据底层连接的主要访问点
-
数据实体
用于表示应用的数据库中的表
-
数据访问对象(DAO)
提供您的应用可用于查询、更新、插入和删除数据库中的数据的方法
Room实体定义数据
当您使用 Room 持久性库存储应用数据时,您可以定义实体来表示要存储的对象。每个实体都对应于关联的 Room 数据库中的一个表,并且实体的每个实例都表示相应表中的一行数据
这意味着您可以使用 Room 实体定义数据库架构,而无需编写任何 SQL 代码
常用注解
-
@Entity
@Entity注解类将对象实体类转换为Room实体数据,其中每个字段对应表中每一列,且每个Room实体的构成都必须包含主键的一个或多个列。Room将类名作为表名,若想修改可通过请设置 @Entity 注解的 tableName 属性。同样,Room 默认使用字段名称作为数据库中的列名称。如果您希望列具有不同的名称,请将 @ColumnInfo 注解添加到该字段并设置 name 属性
@Entity(tableName = "users")
data class User (
@PrimaryKey val id: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
@Entity(tableName = "user")
data class User(
val firstName: String,
val lastName: String,
val age: Int,
val age1: String
) {
@PrimaryKey(autoGenerate = true)
var id: Int = 0
}
-
@PrimaryKey
@PrimaryKey用于Room的主键定义,可通过@PrimaryKey进行单一字段的主键定义,autoGenerate属性定义是否为自增长,也可通过列出 @Entity 的 primaryKeys 属性中的以下列定义一个复合主键
@Entity(tableName = "user")
data class User(
val firstName: String,
val lastName: String,
){
@PrimaryKey(autoGenerate = true)
var id: Int = 0
}
@Entity(primaryKeys = ["firstName", "lastName"])
data class User(
val firstName: String?,
val lastName: String?
)
-
@ColumnInfo
@ColumnInfo用于Room字段修改字段在表中名称
@Entity
data class User (
@PrimaryKey val id: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
-
@Ignore
默认情况下,Room 会为实体中定义的每个字段创建一个列。 如果某个实体中有您不想保留的字段,则可以使用 @Ignore 为这些字段添加注解
@Entity
data class User(
@PrimaryKey val id: Int,
val firstName: String?,
val lastName: String?,
@Ignore val picture: Bitmap?
)
-
@AutoValue
在 Room 2.1.0 及更高版本中,您可以将基于 Java 的不可变值类(使用 @AutoValue 进行注解)用作应用数据库中的实体。此支持在实体的两个实例被视为相等(如果这两个实例的列包含相同的值)时尤为有用
将带有 @AutoValue 注解的类用作实体时,您可以使用 @PrimaryKey、@ColumnInfo、@Embedded 和 @Relation 为该类的抽象方法添加注解。但是,您必须在每次使用这些注解时添加 @CopyAnnotations 注解,以便 Room 可以正确解释这些方法的自动生成实现
@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);
}
}
-
@Fts3/@Fts4
Room 支持多种类型的注解,可让您更轻松地搜索数据库表中的详细信息。除非应用的 minSdkVersion 低于 16,否则请使用全文搜索
如果您的应用需要通过全文搜索 (FTS) 快速访问数据库信息,请使用虚拟表(使用 FTS3 或 FTS4 SQLite 扩展模块)为您的实体提供支持。如需使用 Room 2.1.0 及更高版本中提供的这项功能,请将 @Fts3 或 @Fts4 注解添加到给定实体
// Use `@Fts3` only if your app has strict disk space requirements or if you
// require compatibility with an older SQLite version.
@Fts4
@Entity(tableName = "users")
data class User(
/* 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. */
@PrimaryKey @ColumnInfo(name = "rowid") val id: Int,
@ColumnInfo(name = "first_name") val firstName: String?
)
如果表支持以多种语言显示的内容,请使用 languageId 选项指定用于存储每一行语言信息的列
@Fts4(languageId = "lid")
@Entity(tableName = "users")
data class User(
// ...
@ColumnInfo(name = "lid") val languageId: Int
)
Room 提供了用于定义由 FTS 支持的实体的其他几个选项,包括结果排序、令牌生成器类型以及作为外部内容管理的表
如果您的应用必须支持不允许使用由 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?
)
有时,数据库中的某些字段或字段组必须是唯一的。 您可以通过将 @Index 注解的 unique 属性设为 true,强制实施此唯一性属性
@Entity(indices = [Index(value = ["first_name", "last_name"],
unique = true)])
data class User(
@PrimaryKey val id: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?,
@Ignore var picture: Bitmap?
)
数据访问对象(DAO)
当您使用 Room 持久性库存储应用的数据时,您可以通过定义数据访问对象 (DAO) 与存储的数据进行交互。每个 DAO 都包含一些方法,这些方法提供对应用数据库的抽象访问权限。在编译时,Room 会自动为您定义的 DAO 生成实现
通过使用 DAO(而不是查询构建器或直接查询)来访问应用的数据库,您可以使关注点保持分离,这是一项关键的架构原则。DAO 还可让您在测试应用时更轻松地模拟数据库访问
您可以将每个 DAO 定义为一个接口或一个抽象类。对于基本用例,您通常应使用接口。无论是哪种情况,您都必须始终使用 @Dao 为您的 DAO 添加注解。DAO 不具有属性,但它们定义了一个或多个方法,可用于与应用数据库中的数据进行交互
@Dao
interface UserDao {
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
@Query("SELECT * FROM user")
fun getAll(): List<User>
}
插入数据
借助 @Insert 注释,您可以定义将其参数插入到数据库中的相应表中的方法
@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>)
}
@Insert 方法的每个参数必须是带有 @Entity 注解的 Room 数据实体类的实例或数据实体类实例的集合。调用 @Insert 方法时,Room 会将每个传递的实体实例插入到相应的数据库表中
如果 @Insert 方法接收单个参数,则会返回 long 值,这是插入项的新 rowId。如果参数是数组或集合,则该方法应改为返回由 long 值组成的数组或集合,并且每个值都作为其中一个插入项的 rowId
更新数据
借助 @Update 注释,您可以定义更新数据库表中特定行的方法。与 @Insert 方法类似,@Update 方法接受数据实体实例作为参数
@Dao
interface UserDao {
@Update
fun updateUsers(vararg users: User)
}
Room 使用主键将传递的实体实例与数据库中的行进行匹配。如果没有具有相同主键的行,Room 不会进行任何更改
@Update 方法可以选择性地返回 int 值,该值指示成功更新的行数
删除数据
借助 @Delete 注释,您可以定义从数据库表中删除特定行的方法。与 @Insert 方法类似,@Delete 方法接受数据实体实例作为参数
@Dao
interface UserDao {
@Delete
fun deleteUsers(vararg users: User)
}
Room 使用主键将传递的实体实例与数据库中的行进行匹配。如果没有具有相同主键的行,Room 不会进行任何更改
@Delete 方法可以选择性地返回 int 值,该值指示成功删除的行数
数据查询
使用 @Query 注解,您可以编写 SQL 语句并将其作为 DAO 方法公开。使用这些查询方法从应用的数据库查询数据,或者需要执行更复杂的插入、更新和删除操作
Room 会在编译时验证 SQL 查询
不带参数
获取表中所有数据
@Query("SELECT * FROM user")
fun loadAllUsers(): Array<User>
带参简单查询
获取条件数据
@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>
带一组参数查询
某些 DAO 方法可能要求您传入数量不定的参数,参数的数量要到运行时才知道。Room 知道参数何时表示集合,并根据提供的参数数量在运行时自动将其展开
@Query("SELECT * FROM user WHERE region IN (:regions)")
fun loadUsersFromRegions(regions: List<String>): List<User>
多表联查
您的部分查询可能需要访问多个表格才能计算出结果。您可以在 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?)
}
返回多重映射
在 Room 2.4 及更高版本中,您还可以通过编写返回多重映射的查询方法来查询多个表中的列,而无需定义其他数据类
@Query(
"SELECT * FROM user" +
"JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<User, List<Book>>
查询方法返回多重映射时,您可以编写使用 GROUP BY 子句的查询,以便利用 SQL 的功能进行高级计算和过滤
@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>>
数据库
以下代码定义了用于保存数据库的 AppDatabase 类。 AppDatabase 定义数据库配置,并作为应用对持久性数据的主要访问点。数据库类必须满足以下条件:
-
该类必须带有 @Database 注解,该注解包含列出所有与数据库关联的数据实体的 entities 数组,exportSchema定义是否可导出
-
该类必须是一个抽象类,用于扩展 RoomDatabase
-
对于与数据库关联的每个 DAO 类,数据库类必须定义一个具有零参数的抽象方法,并返回 DAO 类的实例
@Database(entities = arrayOf(PersonnelInformationBean.User::class), version = 1, exportSchema = false)
abstract class AppDatebase : RoomDatabase() {
companion object {
@Volatile
private var mAppDatebase: AppDatebase? = null
fun getInstance(vararg mContext : Context) :AppDatebase{
return mAppDatebase ?: synchronized(this){
val instance = Room.databaseBuilder(mContext[0], AppDatebase::class.java, "room_database").build()
mAppDatebase = instance
instance
}
}
}
abstract fun getUser(): PersonnelInformationBean.User
}
用法
通过Room.databaseBuilder构建出数据库,AppDatebase提供的DAO抽象实例获取DAO中的方法与数据库进行交互
val userDao = mAppDatebase.userDao()
val users: List<User> = userDao.getAll()
数据库版本迭代(字段更新或新增表)
通过Room.databaseBuilder的addMigrations方法新增Migrations
补充:若是有多字段需更新,只能使用多条语句更新
fun getInstance(vararg mContext: Context): AppDatebase {
return mAppDatebase ?: synchronized(this) {
val instance =
Room.databaseBuilder(mContext[0], AppDatebase::class.java, "room_database")
.allowMainThreadQueries()
.addMigrations(migration_1_2, migration_2_3)
.build()
mAppDatebase = instance
instance
}
}
var migration_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
//新增字段
database.execSQL("ALTER TABLE user ADD COLUMN age INTEGER");
}
}
var migration_2_3: Migration = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
//新增字段
database.execSQL("ALTER TABLE user ADD COLUMN age1 TEXT");
//新增表
database.execSQL("CREATE TABLE Student (" +
"id INTEGER PRIMARY KEY," +
"name TEXT," +
"age INTEGER)")
}
}
补充,Room数据线程安全
Room中也添加了rxjava,保证数据线程安全
以下代码定义了用于保证数据操作线程安全的 AppDBDisposable 类
class AppDBDisposable {
companion object {
private val compositeDisposable: CompositeDisposable = CompositeDisposable();
fun <T> addDisposable(flowable: Flowable<T>, consumer: Consumer<T>) {
compositeDisposable.add(
flowable
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(consumer)
)
}
fun addDisposable(completable: Completable, action: Action?) {
compositeDisposable.add(
completable
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(action)
)
}
}
}
用法
数据插入或数据查询返回数据使用room - rxjava2中封装的Completable类型或者Flowable
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun add(mPersonnel: PersonnelInformationBean.User): Completable
@Query("select * from user")
fun getAll(): Flowable<List<PersonnelInformationBean.User>>
val user = AppDatebase.getInstance().getUser()
var addUser: PersonnelInformationBean.User = PersonnelInformationBean.User("涛","刘")
val add = user.add(addUser)
AppDBDisposable.addDisposable(add) {
Log.d(TAG, "onCreate: 数据添加成功")
}
val alls = user.getAll()
AppDBDisposable.addDisposable(alls) { all ->
for (users in all) {
users.firstName
}
}
总结
个人Demo中只是简单的体现了Room框架基础用法,有更多需求可参考官方文档。
官方地址
Demo地址
