zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Room概念与基础使用踩坑

概念基础 使用 Room
2023-09-14 09:13:57 时间

安装配置

请注意我配置的详尽步骤,不然后续出错别怪我

官网提供了详尽的可安装依赖:https://developer.android.google.cn/training/data-storage/room?hl=zh-cn


下面所有操作均在 build.gradle(module:app) 中!

第一步,添加 kapt 插件

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'

    // 添加kapt插件
    id 'kotlin-kapt'
}

第二步,添加完整的 room 依赖

请注意,特别是对于 compose 开发,依赖第二条是必须的!否则直接无法编译并报错!

dependencies {
    def room_version = "2.2.0"

    implementation("androidx.room:room-runtime:$room_version")
    kapt "androidx.room:room-compiler:$room_version"
}

room 版本不要选的太高,譬如你当前项目支持的最低 android 版本为 8.0,那么你直接上最新版 2.5.0 的 room,那直接就报错没什么可说的


概念速通

官方教程:https://developer.android.com/codelabs/kotlin-android-training-room-database?hl=zh_cn#0

创建并调用数据库我们只需要三步

  1. 创建实体类 entity
  2. 创建数据访问对象 DAO
  3. 创建数据库单例

Entity

实体使用一个数据类定义

现定义实体类文件 User.kt
请直接看注释

// 实体类均需使用@Entity注解
// 按照官方建议必须定义表名tableName
@Entity(tableName = "user_table")
data class User(

    // 主键注解@PrimaryKey
    // autoGenerate自增长
    @PrimaryKey(autoGenerate = true)
    val uid: Int=0,

    // 其他键注解@ColumnInfo
    // username是存储到数据表的键名
    // userName是我们开发中使用的变量名
    @ColumnInfo(name = "username")
    val userName: String
)

易错点:
如果我们设置了主键且使其自增长,则必须为其添加一个初始值,否则我们调用该实体类时依然需要填写主键的值


DAO

数据访问对象是一个接口

Room 已经为我们准备了 insert 和 delete 接口,我们仅需传入对应的实体类即可让 Room 自动生成对应的 SQL 语法并执行
而其他的语法则全部使用 Query 接口自己写

定义 DAO 文件 UserDao.kt

// DAO注解不可少
@Dao
interface UserDao{
    // 插入数据
    @Insert
    fun insert(user:User)

    // 删除数据
    @Delete
    fun delete(user: User)

    // 查询数据
    // 这里是查询所有的数据,所以使用List
    @Query("select * from user_table")
    fun getAll():List<User>
}

Database

下方给出的初始化数据库的抽象类模板是基本固定的,可以直接 cv 拿去用!

数据库目前需要两个关键点:

  1. 保证单例,即数据库仅实例化一次
  2. 保证数据库存在时可以拿到数据,不存在时可以自行创建
// 第一步,定义database注解
// 1. entities用到的实体类
// 2. version数据库版本,必须定义,且每次更新数据库都需要修改版本号
// 3. exportSchema是否记录数据库版本
@Database(
    entities = [User::class],
    version = 1,
    exportSchema = false
)
// 继承RoomDatabase
abstract class UserDatabase : RoomDatabase() {
    // 第二步:抽象方法userDao
    abstract fun userDao(): UserDao

    // 第三步:享元实现,保证数据库必须是单例!
    // 保证数据库在被实例化时必须是存在的(若不存在就创建后在返回)
    companion object {
        @Volatile
        private var INSTANCE: UserDatabase? = null

        fun getInstance(context: Context) =
            INSTANCE?: synchronized(this){
                // 创建数据库(若数据库不存在)
                INSTANCE?:Room.databaseBuilder(
                    // 上下文
                    context,
                    // 欲创建的数据库类
                    UserDatabase::class.java,
                    // 数据库名字
                    "user_database"
                )
                    .fallbackToDestructiveMigration()
                    .allowMainThreadQueries()   // 保证可以在主线程被调用
                    .build()
            }
    }
}

开发踩坑

保证仅一次初始化

如果你不愿意拆分出一个单文件用来初始化数据库,而直接在主类中使用它,那么请保证仅初始化一次数据库!

// 全局设置MainActivity上下文
lateinit var mainContext: MainActivity
// 设置全局DAO
lateinit var db: UserDao

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 指定上下文
        mainContext = this
        // 通过当前主类上下文实例化对应的数据库,并获取DAO
        db = UserDatabase.getInstance(mainContext).userDao()

        setContent {
            ...
        }
    }
}

操作在线程内

利用 kotlin 语法糖 thread,保证所有的数据库操作都写在里面

thread {
    db.insert(User(userName = content))
}