Jetpack - Room

本文详细介绍了Android中的ORM框架Room,包括Entity、Dao和Database的使用方法。讲解了如何定义数据模型、主键、列名、忽略参数以及支持全文搜索和创建索引。同时,展示了如何创建Dao接口进行数据的插入、删除、更新和查询,并提供了数据库升级的示例。最后,通过代码示例展示了在实际应用中如何调用Room数据库。
摘要由CSDN通过智能技术生成

一、概念

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 定义数据

数据库实体(数据模型)。对应一张表,表中的列根据实体类中的字段生成。

注解可选配置说明
@EntitytableName默认情况下会将类名用作表名,对类配置@Entity(tableName = "users")配置来更改。(注意SOL中表和列名称不区分大小写)
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默认情况下会将参数名用作表的列名,一个参数对应一个列名,对参数使用@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必须声明为抽象类。

注解可选配置说明
@Databaseverson数据库版本
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") }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值