Android Room 入坑详解

Android Room 入坑详解

Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优势:

针对 SQL 查询的编译时验证。

可最大限度减少重复和容易出错的样板代码的方便注解。

简化了数据库迁移路径。

主要组件

Room 包含三个主要组件:

数据库类,用于保存数据库并作为应用持久性数据底层连接的主要访问点。

数据实体,用于表示应用的数据库中的表。

数据访问对象 (DAO),提供您的应用可用于查询、更新、插入和删除数据库中的数据的方法。

数据库类为应用提供与该数据库关联的 DAO 的实例。反过来,应用可以使用 DAO 从数据库中检索数据,作为关联的数据实体对象的实例。此外,应用还可以使用定义的数据实体更新相应表中的行,或者创建新行供插入。

Room 的不同组件之间的关系

引入Room依赖

首先在build.gradle导入依赖,Kotlin和RxJava可以按需导入,注释处理工具选一个即可。

def room_version = "2.4.2"

implementation"androidx.room:room-runtime:$room_version"

// 注释处理工具

annotationProcessor "androidx.room:room-compiler:$room_version"

// Kotlin注释处理工具(kapt)

kapt"androidx.room:room-compiler:$room_version"

// kotlin扩展和协同程序对Room的支持

implementation "androidx.room:room-ktx:$room_version"

// RxJava2

implementation "androidx.room:room-rxjava2:$room_version"

// RxJava3

implementation "androidx.room:room-rxjava3:$room_version"

如果使用Kotlin注释处理工具(kapt),还需要在build.gradle文件顶部添加下方定义。

apply plugin: 'kotlin-kapt'

在 android 块中添加 packagingOptions 块,以从软件包中排除原子函数模块并防止出现警告。

android {

// other configuration (buildTypes, defaultConfig, etc.)

packagingOptions {

exclude 'META-INF/atomicfu.kotlin_module'

}

// 要使用的一些 API 需要 1.8 `jvmTarget`

kotlinOptions {

jvmTarget = "1.8"

}

}

创建实体

Room 允许通过实体创建表,以下代码定义了一个 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?,

@Ignore val picture: Bitmap?

)

@Entity(tableName = "xxx") 每个 @Entity 类代表一个 SQLite 表。

@PrimaryKey 声明主键,@PrimaryKey(autoGenerate = true)可自动生成唯一的主键。

@ColumnInfo(name = "xxx") 可以指定表中列的名称,默认是字段名称。

存储在数据库中的每个属性均需公开,这是 Kotlin 的默认设置。

如果某个实体中有不想保留的字段,则可以使用 @Ignore 为这些字段添加注解

创建DAO

在 DAO(数据访问对象)中,可以指定 SQL 查询并将其与方法调用相关联,DAO 必须是一个接口或抽象类。默认情况下,所有语句都必须在单独的线程上执行。Room 支持Kotlin 协程,可使用 suspend 修饰符对查询进行注解,然后从协程或其他挂起函数对其进行调用。

以下代码定义了一个名为 UserDao 的 DAO。UserDao 提供了应用的其余部分用于与 user 表中的数据交互的方法。

@Dao

interface UserDao {

@Query("SELECT * FROM user")

fun getAll(): List

@Query("SELECT * FROM user WHERE uid IN (:userIds)")

fun loadAllByIds(userIds: IntArray): List

@Query("SELECT * FROM user WHERE first_name LIKE :first AND " +

"last_name LIKE :last ORDER BY last_name DESC LIMIT 1")

fun findByName(first: String, last: String): User

// onConflict 配置主键冲突处理

@Insert(onConflict = OnConflictStrategy.REPLACE)

fun insertUsers(vararg users: User)

@Insert

fun insertBothUsers(user1: User, user2: User)

@Insert

fun insertUsersAndFriends(user: User, friends: List)

@Delete

fun delete(user: User)

}

如果 @Insert 方法接收单个参数,则会返回 long 值,这是插入项的新 rowId。如果参数是数组或集合,则该方法应改为返回由 long 值组成的数组或集合,并且每个值都作为其中一个插入项的 rowId

@Update 和 @Delete 方法可以选择性地返回 int 值,该值指示成功的行数。

onConflict配置:ABORT:在发生冲突时回滚事务、IGNORE:保留现有行、REPLACE:替换现有行

查看更多sqlite语法

interface BaseDAO {

@Insert

suspend fun insert(obj: T)

@Insert

suspend fun insert(list: List)

@Update

suspend fun update(obj: T)

@Update

suspend fun update(list: List)

@Delete

suspend fun delete(obj: T)

@Delete

suspend fun delete(list: List)

}

除了直接在DAO中写方法,还可以创建DAO的基类,把基础方法提取出来,减少重复代码。

配置数据库

Room 数据库类必须是抽象且必须扩展 RoomDatabase。整个应用通常只需要一个 Room 数据库实例。数据库类必须满足以下条件:

该类必须带有 @Database 注解,该注解包含列出所有与数据库关联的数据实体的 entities 数组。

该类必须是一个抽象类,用于扩展 RoomDatabase。

对于与数据库关联的每个 DAO 类,必须在数据库类中定义一个具有零参数的抽象方法,并返回 DAO 类的实例。

@Database(entities = [User::class], version = 1, exportSchema = false)

abstract class AppDatabase : RoomDatabase() {

abstract fun userDao(): UserDao

companion object {

// 防止同一时间创建多个实例

@Volatile

private var INSTANCE: AppDatabase ? = null

fun getDatabase(context: Context): AppDatabase {

// 单例

return INSTANCE ?: synchronized(this) {

val instance = Room.databaseBuilder(

// 可以使用单例Application来代替此参数传递

context.applicationContext,

AppDatabase::class.java,

"database_name"

).build()

INSTANCE = instance

instance

}

}

}

}

现在数据库已经可以正常使用了,可以通过以下方式操作数据库:

AppDatabase.getDatabase(context).userDao().insertUsers(user)

保存Date等复杂类型数据

Date等类型字段,Room是不知道怎么存的,需要通过转换器来保存和读取。

class Converters {

@TypeConverter

fun fromTimestamp(value: Long?): Date? {

return value?.let { Date(it) }

}

@TypeConverter

fun dateToTimestamp(date: Date?): Long? {

return date?.time?.toLong()

}

}

在数据库类配置注解。

@Database(entities = [User::class], version = 1, exportSchema = false)

@TypeConverters(Converters::class)

abstract class AppDatabase : RoomDatabase()

接下来直接在实体声明Date类型的参数即可,当写的时候会调用dateToTimestamp,读的时候会调用fromTimestamp。

相关推荐