Jetpack - Room

Jetpack - Room 官方页面一、概念ORM框架Object Relational Mapping对象关系映射将面向对象的编程语言和面向关系的数据库之间建立一种映射关系。Entity数据实体数据模型、数据结构对应一张表表中的列根据实体类中的字段生成。实体的每个实例都表示相应表中的一行数据。Dao数据访问对象。必须是接口根据业务封装对数据库操作的抽象方法会在编译时自动生成实现调用时就不用跟底层语法打交道了。Database数据库类。必须是抽象类定义了版本号、包含哪些实体类提供Dao层访问的实例。二、添加依赖[versions] kotlinKsp 2.3.6 room 2.8.4 [libraries] room-runtime { module androidx.room:room-runtime, version.ref room } room-ktx { module androidx.room:room-ktx, version.ref room } room-paging { module androidx.room:room-paging, version.ref room } room-compiler { module androidx.room:room-compiler, version.ref room } [plugins] kotlin-ksp { id com.google.devtools.ksp, version.ref kotlinKsp }Project.build.gradleplugins { alias(libs.plugins.kotlin.ksp) apply false }Module.build.gradleplugins { alias(libs.plugins.kotlin.ksp) } dependencies { implementation(libs.room.runtime) implementation(libs.room.ktx) //协程、扩展函数 支持 implementation(libs.room.paging) //paging3 支持 ksp(libs.room.compiler) }三、创建 Entity 定义表结构实体包含表中的每一列的字段包括构成主键的一个或多个列。注解可选配置说明EntitytableName默认情况下会将类名用作表名使用该配置来更改。SQL中表和列名称不区分大小写indices将数据库中的某些列编入索引以加快查询速度。对类配置Entity(indices [Index(calue [last_name,address])])。primaryKeys需要通过多个列的组合进行唯一标识定义复合主键可对类配置Entity(primaryKeys [fristName,lastName])。ignoredColumns如果实体继承了父类对类配置Entity(ignoredColumns [age])来忽略父类中的字段。PrimaryKeyautoGenerate true主键用于唯一标识每一行对参数使用primaryKey注解来设置需要自增可配置autoGenerate true。Ignore默认情况下会为每个参数创建一个列对参数使用Ignore注解来忽略。ColumnInfoname默认情况下会将参数名用作表的列名一个参数对应一个列名使用改配置来更改。SQL中表和列名称不区分大小写typeAffinity该属性在表中的类型。2.2.1 定义实体类对类使用Entity注解来设置该类为Room识别的实体类。Entity(tableName UserEntity) //不设置默认使用类名作为表名 data class User()2.2.2 定义主键实体必须指定主键主键用于唯一标识每一行对参数使用primaryKey注解来设置需要自增自动分配id可配置autoGenerate true。Entity(primaryKeys [fristName,lastName]) //定义复合主键通过多个列的组合进行唯一标识 data class User( //设为主键自增长 PrimaryKey(autoGenerate true) val id: Long )2.2.3 定义列名默认情况下会将参数名用作表的列名一个参数对应一个列名。需要取别名才用 ColumnInfo 注解指定 name。Entity data class User( //不指定列名默认为参数名 ColumnInfo(name frist_name) val fristName: String? )2.2.4 忽略参数默认情况下会为每个参数创建一个列对参数使用Ignore注解来忽略。如果实体继承了父类对类配置Entity(ignoredColumns [age])来忽略父类中的字段。data class User( Ignore val name: String )open class Father { var age: Int? null } Entity(ignoredColumns [age]) //忽略继承的参数 data class User( PrimaryKey(autoGenerate true) val id: Int ) Father()2.2.5 支持全文搜索需要通过全文搜索 (FTS) 快速访问数据库信息对类使用FTS3或FTS4需要Room2.1.0以上版本。仅当应用有严格的磁盘空间要求或需要与旧版 SQLite 兼容时才使用 Fts3。Fts4 Entity data class User( //为基于 FTS 表的实体指定主键是可选的但若包含主键则必须使用此类型和列名 PrimaryKey ColumnInfo(name rowid) val id: Int )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插入可以直接使用注解只能使用QuerySQL语句Delete删除Update更新Query查询QuerySQL语句2.3.1 Insert Delete Update传入的形参需要是Entity的实例。删除和更新是通过主键进行匹配如果没有相同的行不会进行任何更改。默认不允许在主线程执行数据库操作会抛出IllegalStateException因此使用 suspend 修饰方法配合协程使用。Insert如果方法接收到单个参数则可返回一个 long 值这是插入项的新 rowId。如果参数是数组或集合则可改为返回由 long 值组成的数组或集合并且每个值都作为其中一个插入项的 rowId。Update可以选择性地返回 int 值指示成功更新的行数。Delete 方法可以选择性地返回 int 值指示成功删除的行数。Dao interface UserDao { //插入 Insert suspend fun insertUsers(vararg users: User) Insert suspend fun insertBothUsers(user1: User, user2: User) Insert suspend fun insertUsersAndFriends(user: User, friends: ListUser) //删除 Delete suspend fun deleteUsers(vararg users: User) //更新尝试更新数据库中的一个或多个 User 对象 Update suspend fun updateUsers(vararg users: User) }2.3.2 QuerySQL语句编写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(): ListFullName //查询语函数返回该简单对象2.3.4 将参数传递给查询通常需要接受传参以便进行过滤。//单个参数 Query(SELECT * FROM user WHERE age :minAge) fun getAllUsersOlderThan(minAge: Int): ArrayUser //多个参数 Query(SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge) fun getAllUsersBetween(minAge: Int, maxAge: Int): ArrayUser //多次引用同一参数 Query(SELECT * FROM user WHERE frist_name LIKE :str OR last_name LIKE :str) fun getUsersWithName(str: String): ListUser //一组参数 Query(SELECT * FROM user WHERE region IN (:regions)) fun getUsersFromRegions(regions: ListString): ListUser2.4 创建 Database 配置数据库Database必须声明为抽象类。存在于 data/data/包名/databases/。注解可选配置说明Databaseverson数据库版本。entities包含的实体类。exportSchema是否支持导出默认false要改为true便于后续数据迁移和版本管理。Database(version 1, entities [User::class]) //指定数据库版本与Entity进行关联 abstract class UserDatabase : RoomDatabase() { abstract fun userDao(): UserDao //与Dao进行关联 companion object { //单例因为全局只存在一份数据库 private var instance: UserDatabase? null Synchronized //同步处理 fun getInstance(context: Context): UserDatabase instance ?: Room.databaseBuilder( context context.applicationContext, //上下文使用Application避免内存泄漏 klass UserDatabase::class.java, //本类名称 name UserDB //数据库名称 ).build() } }2.5 数据库升级当数据库版本变高时如果未设置迁移策略默认会删除旧数据库并创建新的会导致数据丢失。需要实现 Migration 类编写 SQL 迁移语句在创建数据库时添加迁移规则。若版本跨度大1→3需要依次实现 MIGRATION_1_2 和 MIGRATION_2_3Room会自动按顺序执行。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(MyApplication.context) } val dao by lazy { db.personDao() } suspend fun getData() { val id dao.insertPerson(Person(null, , 12)) println(查ID${db.personDao().queryPerson(id)}) val list dao.getAllPerson() list.forEach { println(查所有$it) } } }