教程 · 2019年12月21日 0

Android数据库框架 Room的使用 AndroidX+Kotlin

内容目录

去年开始,Google官方推荐使用Room框架访问数据库而不是直接使用SQLite,因此我在这里把Room的使用总结一下。

简介

Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。

  简单来说,Room就是SQLite的一个封装,它充分利用注解,在编译期间自动生成查询的代码。我们要做的仅仅是在JavaBean上加入合适的注解,一个数据表就自动生成了。
  Room可以和RxJava,Guava配合使用,这里不做介绍(因为我也不会用)
本篇使用Android Studio 3.5.3,使用Kotlin插件

使用

准备步骤

要使用Room,需要在build.gradle里添加依赖库
在dependencies中声明:

    def room_version = "2.2.1"
    implementation "androidx.room:room-runtime:$room_version" // 运行时
    kapt "androidx.room:room-compiler:$room_version" // 解释器,用于在编译期间生成查询代码,不会编译进apk
    // 可选 - Kotlin Extensions and Coroutines support for Room
    implementation "androidx.room:room-ktx:$room_version"

使用kapt还需在build.gradle开头声明插件

    apply plugin: 'kotlin-kapt'

除此之外,还需要指定注解处理器的参数

    android {
        //...
        defaultConfig {
            //...
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [
                        "room.schemaLocation":"$projectDir/schemas".toString(),
                        "room.incremental":"true",
                        "room.expandProjection":"true"]
                }
            }
        }
    }

了解结构

Room 包含 3 个主要组件:

数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。
Entity:表示数据库中的表。
DAO:(Data Access Object)包含用于访问数据库的方法。

应用使用 RoomDatabase 来获取与该数据库关联的数据访问对象Dao。然后,应用使用每个Dao从数据库中获取Entity,然后再将对这些Entity的所有更改保存回数据库中。最后,应用使用Entity来获取和设置与数据库中的表列相对应的值。

graph TD;
RoomDatabase-->|获取DAO| App
App --> RoomDatabase
Dao -->|读写Entity对象| App
App --> Dao
Entities -->|读写基本数据| App
App --> Entities

画的有些丑,见谅

编写代码

  1. 编写Entity:

以User为例

@Entity // 默认以entity的类型名(小写)做为数据表名,或者指定 @Entity(tableName = "my_user")
data class User(
    @PrimaryKey(autoGenerate = true) // autoGenerate属性 将在插入时自动增加
    val uid: Int, // 默认以变量名小写为列名,驼峰式会自动转为下划线式

    @ColumnInfo(name = "user_name", index = true) // index 创建索引
    val userName: String,

    @ColumnInfo(name = "user_pass")
    val password: String,

    @ColumnInfo(name = "nick_name")
    val nickName: String? // kotlin中的可空自动识别为sqlite的可空
)
  1. 编写Dao:
@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

    @Query("SELECT COUNT(*) FROM `user` WHERE user_name = :userName")
    fun hasUser(userName: String): Boolean

    // 增
    @Insert
    fun insertAll(vararg users: User)

    // 删
    @Delete
    fun delete(user: User)

    // 改
    @Update
    fun updateUsers(vararg users: UserEntity): Int
    @Update
    fun updateUsers(users: List<User>): Int // 这样重载也能识别
}

编写完上面的代码后,运行一下Ctrl+F9,就可以在 app/build/generated/source/kapt/debug/[package_name]/UserDao_Impl.java里看到生成的代码了

  1. 编写RoomDatabase:
@Database(entities = arrayOf(User::class), version = 1) // 定义版本号,便于升级管理
abstract class MyDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao // 获取DAO
}

下面,就可以使用Room访问数据库了

val db = Room.databaseBuilder(
    applicationContext,
    MyDatabase::class.java, "数据库文件名.db" // 创建数据库比较消耗资源,可以在Application加载时加载数据库,这样保证Database为单例加载模式,若App有多个进程,需要取消下面这行注释
)
    //.enableMultiInstanceInvalidation()
    .build()
val userDao = db.userDao()
var users = userDao.getAll() // 读取数据
for (user: User in users) {
    // 修改数据
}
userDao.updateUsers(users) // 提交修改

进阶用法

从资源、文件填充数据

即复制某个现有的数据库到应用的database目录,然后使用它
这个比较简单,直接在创建Database实例时指定路径即可

Room.databaseBuilder(appContext, MyDatabase::class.java, "Sample.db")
    .createFromAsset("database/myapp.db") // 从assets目录复制
    .createFromFile(File("mypath")) // 从文件复制
    .build()

需要注意的是,Room在会验证数据库的结构是否与代码匹配,所以需要正确配置room.schemaLocation参数

升级数据库结构

编写 Migration 类,指定一个 startVersion 和 endVersion,如下

// 从version1升级到version2
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))")
    }
}

// 从version2升级到version3
val MIGRATION_2_3 = object : Migration(2, 3) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER")
    }
}

// 从version1升级到version3
val MIGRATION_1_3 = object : Migration(1, 3) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))")
        database.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER")
    }
}

在创建数据库实例时传入对应参数

Room.databaseBuilder(applicationContext,
                     MyDatabase::class.java,
                     "database-name")
    .addMigrations(MIGRATION_1_2,
                   MIGRATION_2_3,
                   MIGRATION_1_3)
    .build()

这样在升级应用时,重新创建数据会保留原来的数据,并升级新的数据库结构

引用复杂数据

//TODO