去年开始,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
画的有些丑,见谅
编写代码
- 编写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的可空
)
- 编写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
里看到生成的代码了
- 编写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
近期评论