一、概念
ORM框架:Object Relational Mapping(对象关系映射),将面向对象的编程语言和面向关系的数据库之间建立一种映射关系。
Entity | 数据库实体(数据模型)。对应一张表,表中的列根据实体类中的字段生成。 |
Dao | 数据库访问对象。必须是接口,根据业务封装对表操作的抽象方法会在编译时自动生成实现,调用时就不用跟底层语法打交道了。 |
Database | 数据库类。必须是抽象类,定义了版本号、包含哪些实体类,提供Dao层访问的实例。 |
二、使用
- SQL中表和列名称不区分大小写;
- Room必须拥有参数的访问权限(设为公开或提供getter&setter);
2.1 添加依赖
plugins {
id 'kotlin-kapt'
}
dependencies {
def room_version = "2.4.3"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
kapt "androidx.room:room-compiler:$room_version" // To use Kotlin annotation processing tool (kapt)
}
2.2 创建 Entity 定义数据
数据库实体(数据模型)。对应一张表,表中的列根据实体类中的字段生成。
注解 | 可选配置 | 说明 |
@Entity | tableName | 默认情况下会将类名用作表名,对类配置@Entity(tableName = "users")配置来更改。(注意SOL中表和列名称不区分大小写) |
indices | 用来加快查询速度,对类配置@Entity(indices = [Index(calue = ["last_name","address"])])。 | |
primaryKeys | 需要通过多个列的组合进行唯一标识(定义复合键)可对类配置@Entity(primaryKeys = ["fristName","lastName"])。 | |
ignoredColumns | 如果实体继承了父类,对类配置@Entity(ignoredColumns = ["age"])来忽略父类中的字段。 | |
@PrimaryKey | autoGenerate = true | 主键用于唯一标识每一行,对参数使用@primaryKey注解来设置,需要自增可配置autoGenerate = true。 |
@Ignore | 默认情况下会为每个参数创建一个列,对参数使用@Ignore注解来忽略。 | |
@ColumnInfo | name | 默认情况下会将参数名用作表的列名,一个参数对应一个列名,对参数使用@ColumnInfo来更改。(注意SOL中表和列名称不区分大小写) |
typeAffinity | 该属性在表中的类型。 |
2.2.1 定义实体类
对类使用@Entity注解来设置该类为Room识别的实体类。默认情况下会将类名用作表名,对类配置@Entity(tableName = "users")配置来更改。(注意SOL中表和列名称不区分大小写)
@Entity(tableName = "users") //不设置默认使用类名作为表名
data class User()
2.2.2 定义主键
主键用于唯一标识每一行,对参数使用@primaryKey注解来设置,需要自增可配置autoGenerate = true。需要通过多个列的组合进行唯一标识(定义复合键)可对类配置@Entity(primaryKeys = ["fristName","lastName"])。
@Entity(primaryKeys = ["fristName","lastName"]) //定义复合键
data class User(
@PrimaryKey(autoGenerate = true) val id: Long //设为主键自增长
)
2.2.3 定义列名
默认情况下会将参数名用作表的列名,一个参数对应一个列名,对参数使用@ColumnInfo来更改。(注意SOL中表和列名称不区分大小写)
@Entity
data class User(
@ColumnInfo(name = "frist_name") val fristName: String? //不指定列名默认为参数名
)
2.2.4 忽略参数
默认情况下会为每个参数创建一个列,对参数使用@Ignore注解来忽略。如果实体继承了父类,对类配置@Entity(ignoredColumns = ["age"])来忽略父类中的字段。
open class Father {
var age: Int? = null
}
@Entity(ignoredColumns = ["age"]) //忽略继承的参数
data class User() : Father() {
@Ignore val price: Double? //忽略该参数不会生成列,
}
@Entity
data class Person(
@Ignore val name: String? //如果放在主构造中
var age: Int? //其他字段不能为val,否则抛异常Cannot find setter for field.
) {
constructor() : this(null) //会需要提供一个次构造
}
2.2.5 支持全文搜索
需要通过全文搜索 (FTS) 快速访问数据库信息,对类使用@FTS3或@FTS4,需要Room2.1.0以上版本。
@Fts4
@Entity
data class User()
2.2.6 将特定列编入索引
用来加快查询速度,对类配置@Entity(indices = [Index(calue = ["last_name","address"])])。
@Entity(indices = [Index(value = ["name","address"])])
data class User(
val name: String?,
val address: String?
)
2.3 创建 Dao 访问数据
数据库访问对象。必须是接口,根据业务封装对表操作的抽象方法会在编译时自动生成实现,调用时就不用跟底层语法打交道了。
注解 | 说明 | 函数参数使用Entity实例 | 函数参数使用非Entity实例 |
@Insert | 插入 | 可以直接使用注解 | 只能使用@Query+SQL语句 |
@Delete | 删除 | ||
@Update | 更新 | ||
@Query | 查询 | @Query+SQL语句 |
2.3.1 @Insert @Delete @Update
传入的形参需要是Entity的实例。删除和更新是通过主键进行匹配,如果没有相同的行不会进行任何更改。
@Dao
interface UserDao {
//插入
@Insert
fun insertUsers(vararg users: User)
@Insert
fun insertBothUsers(user1: User, user2: User)
@Insert
fun insertUsersAndFriends(user: User, friends: List<User>)
//删除
@Delete
fun deleteUsers(vararg users: User)
//更新
@Update
fun updateUsers(vararg users: User)
}
2.3.2 @Query+SQL语句
编写SQL语句从而进行查询数据,或更复杂的插入、更新和删除。Room会在编译时自动验证查询语句,如果查询有问题会出现编译错误。
@Dao
interface UserDao {
//使用非实体类参数,只能用@Query加上SQL语句
@Query("delete from Person where age = :age")
fun deleteByPersonName(age: Int): Int //根据传入的年龄删除表中对应的数据
}
2.3.3 获取列的子集
有时候需要一次性获取某几列的数据,可以从查询返回简单对象,前提是可以将这些数据映射到返回的对象中。
//定义数据类,设置对应的字段
data class FullName(
@ColumnInfo(name = "frist_name") val fristName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
//ROOM知道查询会返回frist_name,last_name这两个列的值,会被映射到FullName类的字段中
//如果查询返回的列未映射到返回对象的字段中,会显示警告
@Query("SELECT frist_name,last_name FROM Student")
fun getFullName(): List<FullName> //查询语函数返回该简单对象
2.3.4 将参数传递给查询
通常需要接受传参以便进行过滤。
//单个参数
@Query("SELECT * FROM user WHERE age > :minAge")
fun getAllUsersOlderThan(minAge: Int): Array<User>
//多个参数
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
fun getAllUsersBetween(minAge: Int, maxAge: Int): Array<User>
//多次引用同一参数
@Query("SELECT * FROM user WHERE frist_name LIKE :str OR last_name LIKE :str")
fun getUsersWithName(str: String): List<User>
//一组参数
@Query("SELECT * FROM user WHERE region IN (:regions)")
fun getUsersFromRegions(regions: List<String>): List<User>
2.4 创建 Database 配置数据库
Database必须声明为抽象类。
注解 | 可选配置 | 说明 |
@Database | verson | 数据库版本 |
entities | 包含的实体类 | |
exportSchema = false | 是否支持导出 |
@Database(version = 1, entities = [User::class]) //指定数据库版本,与Entity进行关联
abstract class SemerWeatherDatabase : RoomDatabase() {
abstract fun userDao(): UserDao //与Dao进行关联
companion object { //单例因为全局只存在一份数据库
private var instance: SemerWeatherDatabase? = null
@Synchronized //同步处理
fun getInstance(context: Context): SemerWeatherDatabase = instance
?: Room.databaseBuilder(
context.applicationContext,
SemerWeatherDatabase::class.java,
"SemerWeatherDB" //数据库名称
).build()
}
}
2.5 数据库升级
2.5.1 新增一张表
@Entity
data class Student(
@PrimaryKey(autoGenerate = true) val id: Long
)
@Dao
interface StudentDao {
@Delete
fun deleteStudent(student: Student) //将传入的Student对象从表中删除
}
@Database(version = 2, entities = [User::class, Student::class]) //版本号升级到了2,后添加的Entity也要关联
abstract class SemerWeatherDatabase : RoomDatabase() {
abstract fun UserDao(): UserDao
abstract fun studentDao(): StudentDao //后添加的Dao也要关联
companion object {
private var instance: SemerWeatherDatabase? = null
val MIGRATION_1_2 = object : Migration(1, 2) { //1升2的时候就要执行这个逻辑
override fun migrate(database: SupportSQLiteDatabase) {
//建表语句需要和后添加的Entity中声明的结构完全一致,否则报错
database.execSQL("create table Student (id long primary key autoincrement not null)")
}
}
@Synchronized
fun getInstance(context: Context): SemerWeatherDatabase = instance
?: Room.databaseBuilder(
context.applicationContext,
SemerWeatherDatabase::class.java,
"SemerWeatherDB"
).addMigrations(MIGRATION_1_2) //对数据库进行分配置
.build()
}
}
2.5.2 原表新增列
@Entity
data class Student(
@PrimaryKey(autoGenerate = true) val id: Long,
val name: String
)
@Database(version = 3, entities = [User::class, Student::class]) //版本号升级到了3
abstract class SemerWeatherDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun studentDao(): StudentDao
companion object {
private var instance: SemerWeatherDatabase? = null
//新增
val MIGRATION_2_3 = object : Migration(2, 3) { //2升3的时候就要执行这个逻辑
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table Student add column name text not null default 'unknown'")
}
}
@Synchronized
fun getInstance(context: Context): SemerWeatherDatabase = instance
?: Room.databaseBuilder(
context.applicationContext,
SemerWeatherDatabase::class.java,
"SemerWeatherDB"
).addMigrations(MIGRATION_1_2, MIGRATION_2_3) //对数据库进行分配置
.build()
}
}
2.6 代码中调用
class Repository {
val db by lazy { PersonDatabase.getInstance(GlobalApplication.context) }
val dao by lazy { db.personDao() }
fun getData() {
val id = dao.insertPerson(Person(null, "", 12))
println("查ID:${db.personDao().queryPerson(id)}")
val list = dao.getAllPerson()
list.forEach { println("查所有:$it") }
}
}