Room

2 阅读3分钟

前言

Room 在 SQLite 的基础上提供了一个抽象层,除了拥有 SQLite 的全部功能,使用起来还非常方便。

具体来说,Room 有以下优势:

    1. 编译期对 SQL 查询进行校验;
    1. 提供方便的注解,减少重复且容易出错的样板代码;
    1. 简化的数据库迁移路径;

因为这些优势,强烈建议使用 Room 而不是直接使用 SQLite APIs.

主要组成部分

Room 包含 3 个主要组成部分:

  • 数据库类(database class),持有数据库,作为访问应用存储的数据的底层连接的主要入口。
  • Data entities,代表你的应用的数据库里面的表。
  • Data access objects (DAOs),提供函数用于 查询、更新、插入和删除 数据库。

这 3 个组件的概念也出现在其他 ORM(Object Relational Mapping——对象关系映射)框架中,通过 Database 获取 DAO ,通过 DAO 查询并获取 Entities,最后通过 Entities 对数据库表中的数据进行读写。

简单使用

1. 引入依赖

使用 Room 需引入如下依赖:

dependencies {
    val room_version = "2.5.2"
    // 基础依赖
    implementation("androidx.room:room-runtime:$room_version")
    
    // 如果该项目使用了 Kotlin,请使用 Kotlin 符号处理器 (Kotlin Symbol Processing)
    ksp("androidx.room:room-compiler:$room_version")

    // 如果该项目只用了 Java,使用 Java 的注解处理器(annotationProcessor)
    annotationProcessor("androidx.room:room-compiler:$room_version")

    // 可选项 - 为 Room 提供 Kotlin 扩展功能与协程支持
    implementation("androidx.room:room-ktx:$room_version")
}

使用 KSP 还需要给你的项目添加 KSP Plugin,先去项目中的 build.gradle.kts 中声明 KSP Plugin:

plugins {
    id("com.google.devtools.ksp") version "2.0.0-1.0.21" apply false
}

注意 KSP 需要与 Kotlin 的版本对齐,如果你使用的是 Kotlin 2.0.0,那么 KSP 的版本必须是 2.0.0-x.y.z。

然后在模块的 build.gradle.kts 中启用 KSP:

plugins {
    id("com.google.devtools.ksp")
}

2.定义实体类 Entity

该 Entity 对应数据库中的表

@Entity
data class User(
    // 主键,自增
    @PrimaryKey(autoGenerate = true)
    var userId: Int = 0,
    @ColumnInfo(name = "name")
    var name: String = "",
    // name 表示表中对应的名字,可以定义成一个不同的名字
    @ColumnInfo(name = "pwd")
    var password: String = "",
    @ColumnInfo(name="address")
    var address: String = ""
)

另外定义一个类 UserTuple,用于只获取表中的 name 和 pwd 字段:

@Entity
data class UserTuple(
    @ColumnInfo(name = "name")
    var name: String,
    @ColumnInfo(name = "pwd")
    var password: String,
)

3.定义 DAO 用于对数据库进行增删改查

@Dao
interface UserDao {
    @Insert
    fun insert(vararg users: User?)

    @Delete
    fun delete(user: User?)

    @Update
    fun update(user: User?)

    @Query("select * from User")
    fun queryAll(): List<User?>?

    @Query("select * from User where name like :name")
    fun queryByName(name: String?): User?

    @Query("select * from User where userId in (:userIds)")
    fun queryInUserId(userIds: IntArray?): List<User?>?

    // 只查询 name, pwd 字段
    @Query("select name, pwd from User")
    fun getRecord(): List<UserTuple?>?
}

4.定义数据库类用于获取 Dao

// entities 指定了这个数据库包含的实体类(即表),可以包含多个实体类,用逗号分隔。
// exportSchema 这个参数表示是否导出数据库的架构。
// 通常在生产环境中设置为 false,表示不导出;
// 而在开发和调试时可以设置为 true,以便生成数据库的架构文件。
@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDatabase: RoomDatabase() {
    abstract fun getUserDao(): UserDao
}

5.对数据库进行操作

class MainActivity : AppCompatActivity() {

    private val TAG = this::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        thread {
            val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java,"user")
                //.allowMainThreadQueries()  可以允许在主线程查询
                .build()
            val dao = db.getUserDao()
            dao.insert(User(name = "张三", password = "111", address = "北京市"))
            dao.insert(User(name = "李四", password = "123", address = "天津市"))
            dao.insert(User(name = "王五", password = "456", address = "南京市"))

            val userList = dao.queryAll()
            Log.i(TAG, userList.toString())

            val user = dao.queryByName("王五")
            Log.i(TAG, user.toString())

            val listId = dao.queryInUserId(intArrayOf(2, 3))
            Log.i(TAG, listId.toString())

            val records = dao.getRecord()
            Log.i(TAG, records.toString())
        }
    }
}

运行后打印如下:

[User(userId=1, name=张三, password=111, address=北京市), User(userId=2, name=李四, password=123, address=天津市), User(userId=3, name=王五, password=456, address=南京市)]
User(userId=3, name=王五, password=456, address=南京市)
[User(userId=2, name=李四, password=123, address=天津市), User(userId=3, name=王五, password=456, address=南京市)]
[UserTuple(name=张三, password=111), UserTuple(name=李四, password=123), UserTuple(name=王五, password=456)]