AndroidX Jetpack Room 使用介绍

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地址

猜你喜欢

转载自blog.csdn.net/duanchuanzhi/article/details/129690939