Android笔记(二十一):Room组件实现Android应用的持久化处理

一、Room组件概述

Room是Android JetPack架构组件之一,是一个持久处理的库。Room提供了在SQLite数据库上提供抽象层,使之实现数据访问。
在这里插入图片描述

(1)实体类(Entity):映射并封装了数据库对应的数据表中对应的结构化数据。实体定义了数据库中的数据表。实体类中的数据域与表的列一一对应。
(2)数据访问对象(Data Access Object,DAO):在DAO中定义了访问数据库的常见的操作(例如插入、删除、修改和检索等),以达到实现创建映射数据表的实体类对象,以及对该实体类对象实例的属性值进行设置和获取的目的。
(3)数据库(Room Database):表示对数据库基本信息的描述,包括数据库的版本、名称、包含的实体类和提供的DAO对象实例。Room组件中的所有的数据库必须扩展为RoomDatabase抽象类,从而实现对实际SQLite数据库的封装。

二、Room组件的配置

在移动应用所在的模块对应的build.gradle中需要进行如下配置:

(1) 增加插件

Groovy DSL:

plugins {
	   ……
    id 'kotlin-kapt'
}

Kotlin DSL:

plugins{
...
  id("kotlin-kapt")
}

kotlin-kapt实现标注(注解)处理

(2)增加标注处理的配置

Groovy DSL定义:

android {
   ……
    defaultConfig {
       ……
        //增加标注处理,增加Schema保存的路径
        javaCompileOptions{
            annotationProcessorOptions{//定义标注处理器选项
                arguments +=[
                        "room.schemaLocation":"$projectDir/schemas".toString(),
                        "room.incremental":"true",
                        "room.expandProjection":"true"
                ]
            }
        }
}

Kotlin DSL定义:

android {
   ……
    defaultConfig {
       ……
        //增加标注处理
        javaCompileOptions{
            annotationProcessorOptions{
                //定义标注处理器选项
                arguments +=mapOf(
                    "room.schemaLocation" to "$projectDir/schemas".toString(),
                "room.incremental" to "true",
                "room.expandProjection" to "true"
                )
            }
        }
}

(3)增加相关依赖

Groovy DSL定义

    def room_version = "2.5.0"
    implementation"androidx.room:room-runtime:$room_version"
    // 注释处理工具
    annotationProcessor "androidx.room:room-compiler:$room_version"
    // Kotlin注释处理工具(kapt)
    kapt"androidx.room:room-compiler:$room_version"
    // kotlin扩展和协同程序对Room的支持
    implementation "androidx.room:room-ktx:$room_version"

如果在处理数据库是需要采用rxJava3来实现异步处理,这时还需要增加RxJava3库

    //增加RxJava库的依赖
    implementation "io.reactivex.rxjava3:rxjava:3.0.7"
    //增加在Android对RxJava库的支持
    implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
    // RxJava3
    implementation "androidx.room:room-rxjava3:$room_version"

Kotlin DSL定义依赖:

    val room_version = "2.5.0"
    implementation("androidx.room:room-runtime:$room_version")
    annotationProcessor("androidx.room:room-compiler:$room_version")
    kapt("androidx.room:room-compiler:$room_version")

    // kotlin扩展和协同程序对Room的支持
    implementation("androidx.room:room-ktx:$room_version")
    // RxJava2
    implementation("androidx.room:room-rxjava2:$room_version")
    // RxJava3
    implementation("androidx.room:room-rxjava3:$room_version")
    //增加RxJava库的依赖
    implementation("io.reactivex.rxjava3:rxjava:3.0.7")
    //增加在Android对RxJava库的支持
    implementation("io.reactivex.rxjava3:rxandroid:3.0.0")

如果在构建模块的过程中,出现了java版本不兼容的情况,可以调整:

android{
...
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = "17"
    }
}

三、Room组件实现数据库的处理

新建一个项目,实现对多位学生的信息写入数据库并执行检索和CRUD操作。

3.1 创建实体类

映射并封装了数据库对应的数据表中对应的结构化数据。实体定义了数据库中的数据表。实体类中的数据域与表的列一一对应。

@Entity(tableName = "students")
data class Student(@PrimaryKey(autoGenerate = true)
                   @ColumnInfo(name= "studentId") val id:Long,
                   @ColumnInfo(name= "studentNo") val no:String?,
                   @ColumnInfo(name= "studentName") val name:String,
                   @ColumnInfo(name= "studentScore") val score:Int,
                   @ColumnInfo(name = "studentGrade") val grade:String?
                   )
{
    @Ignore
    constructor(no:String,name:String,score:Int,grade:String):
            this(0,no,name,score,grade)
}

定义的实体类Student与数据表students对应。通过标注@Entity(tableName = “students”)来指定实体类对应的数据表。并对实体类的属性定义通过标注@ColumnInfo,对应于数据表students中的各个字段,并通过@PrimaryKey标注来指定数据表的关键字。

注意:Room只能识别和使用一个构造器,如果存在多个构造器可以使用@Ignore让Room忽略这个构造器。因此在上述代码中constructor定义的辅助构造器增加了标注@Ignore。

3.2 创建数据访问对象DAO

在数据访问对象DAO是一个接口,定义了对指定数据表希望能执行的CRUD操作。

@Dao
interface StudentDAO {
    /**
     * 插入记录
     */
    @Insert
    fun insertStudent(student:Student):Long

    /**
     * 删除记录
     */
    @Update
    fun updateStudent(student:Student)

    /**
     * 删除记录
     */
    @Delete
    fun deleteStudent(student:Student)

    /**
     * 检索所有的记录
     */
    @Query("select * from students")
    fun queryAllStudents():List<Student>

    /**
     * 检索指定学号的学生记录
     */
    @Query("select * from students where studentNo = :no")
    fun queryStudentByNo(no:String):Student

}

3.3 创建数据库

必须定义一个RoomDatabase的抽象子类来表示对数据库基本信息的描述,包括数据库的版本、名称、包含的实体类和提供的DAO对象实例。通过数据库类来达到对实际SQLite数据库的封装。

@Database(entities = [Student::class], version = 1)
abstract class StudentDatabase : RoomDatabase() {
    abstract fun studentDao(): StudentDAO

    companion object{
        private var instance: StudentDatabase? = null
        /**
         * 单例模式创建为一个StudentDatabase对象实例
         */
        @Synchronized
        fun getInstance(context: Context): StudentDatabase {
            instance?.let{
                return it
            }
            return Room.databaseBuilder(
                context,
                StudentDatabase::class.java,
                "studentDB.db"
            ).build()
        }
    }
}

@Database标注表示抽象的类对应数据库,内部包括的数据表由标注内部的属性entities指定。如果数据库包括多个数据表,entitites可以指定多个实体类的类对象。
在上述的代码中,采用了单例模式,使得在整个移动应用中只有一个数据库的对象实例,在获取这个唯一实例时,只有一个线程可以访问,因此在getInstance方法中设置标注@Synchronized。在这个方法指定创建的数据库名是studentDB.db

3.4 定义并配置应用类

因为在应用中需要获取上下文,因此定义应用类,并在AndroidManifest进行配置,使之易于获取applicationContext上下文对象。

3.4.1定义应用类

class MainApp: Application() {
    @SuppressLint("StaticFieldLeaked")
    companion object{
        lateinit var context: Context
    }

    override fun onCreate() {
        super.onCreate()
        context = applicationContext
    }
}

3.4.2 在AndroidManifest.xml配置应用类

在AndroidManifest.xml中需要在application元素中指定已定义的应用类MainApp,类似如下代码

 <?xml version="1.0" encoding="utf-8"?>   
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application  android:name=".MainApp"  ... > 
</application>    
</manifest>  

3.5 测试数据库的访问

在MainActivity中定义对数据库的测试代码。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
                    testDB()
                }
    }
    /**
     * 测试数据库
     */
    fun testDB() {
        Observable.create<Student> { emitter ->
            //获得Dao对象
            val dao = StudentDatabase.getInstance(MainApp.context).studentDao()
            //插入记录,测试数据库版本,将下列注释取消
            dao.insertStudent(Student("6001013", "李四", 87, "良好"))
            //检索记录
            val students = dao.queryAllStudents()
            for (student in students)
                emitter.onNext(student)
        }.subscribeOn(Schedulers.io())//指定被观察者的线程处理I/O 操作
            .observeOn(AndroidSchedulers.mainThread())//指定观察者的线程为主线程
            .subscribe {
                Log.d("Ch10_05", "${it}")
            }
      }
}

四、Room组件实现数据库的迁移

移动应用的需求的变化,也会导致数据库不断地升级。在数据库升级时,会希望保留原有的数据。因此,Room提供了数据库迁移的方式来解决数据库的升级。

Room库提供了Migration 类实现数据库增量迁移。每个 Migration 子类提供了Migration.migrate() 函数实现新旧版本数据库之间的迁移路径。当移动应用需要升级数据库时,Room 库会利用一个或多个 Migration 子类运行 migrate() 函数,在运行时将数据库迁移到最新版本。

在上述的模块的基础上,要求修改数据库中数据表students的结构,增加一个新的字studentAddress,这时需要修改上述代码来完成具体的功能。

4.1 修改实体类

修改实体类Student,增加一个属性address,并映射数据表students的字段studentAddress,代码如下:

@Entity(tableName = "students")
data class Student(@PrimaryKey(autoGenerate = true)
                   @ColumnInfo(name="studentId") val id:Long,
                   @ColumnInfo(name="studentNo") val no:String?,
                   @ColumnInfo(name="studentName") val name:String,
                   @ColumnInfo(name="studentScore") val score:Int,
                   @ColumnInfo(name = "studentGrade") val grade:String?,
                   @ColumnInfo(name="studentAddress") val address:String?){

    @Ignore
    constructor(no:String,name:String,score:Int,grade:String,address:String):
            this(0,no,name,score,grade,address)
}

4.2 修改数据库

因为数据表变化,这时需要修改数据库,变更数据库的版本为2。定义Migration对象,指定数据库迁移是从版本1迁移到版本2,并覆盖migrate的方法,执行具体迁移的操作。

@Database(entities = [Student::class], version = 2)
abstract class StudentDatabase : RoomDatabase() {
    abstract fun studentDao(): StudentDAO
    companion object{
        private var instance: StudentDatabase? = null
        //数据库从版本1迁移到版本2
        val MIGRATION_1_2 = object : Migration(1, 2) {
            //迁移方法定义
            override fun migrate(database: SupportSQLiteDatabase) {
           //修改数据表students,增加一个新的字段address,数据类型为TEXT字符串
           database.execSQL("ALTER TABLE students ADD COLUMN studentAddress TEXT")
            }
        }

        /**
         * 单例模式创建为一个StudentDatabase对象实例
         */
        @Synchronized
        fun getInstance(context:Context):StudentDatabase{
            instance?.let{
                return it
            }
            return Room.databaseBuilder(
                context,
                StudentDatabase::class.java,
                "studentDB.db")
                .addMigrations(MIGRATION_1_2).build().apply{
                    instance = this
                }
        }
    }
}

在上述代码的getInstance返回数据库对象时,通过调用addMigrations进行处理迁移的操作。

4.3 修改测试代码

在上述修改的前提基础上,因数据库的变更,测试代码也进行修改,代码如下:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
                      testDB()
                }
    }
 
    /**
     * 数据库版本2的测试函数
     */
    fun testDB(){
        Observable.create<Student>{ emitter ->
            //获得Dao对象
            val dao = StudentDatabase.getInstance(MainApp.context).studentDao()
            //插入记录
            dao.insertStudent(Student("6001015","王五",87,"良好","江西省南昌红谷大道999号"))
            //检索记录
            val students = dao.queryAllStudents()
            for(student in students)
                emitter.onNext(student)
        }.subscribeOn(Schedulers.io())//指定被观察者的线程处理I/O 操作
         .observeOn(AndroidSchedulers.mainThread())//指定观察者的线程为主线程
         .subscribe{ it: Student ->
                Log.d("TAG","${it}")
            }
     }
}

参考文献

陈轶《Android移动应用开发(微课版)》[M] 北京:清华大学出版社 2022 P407-P419

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/271142.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序备案流程整理

一、备案流程 [找备案入口]–[填主体信息]–[填小程序信息]–[初审]–[短信核验]–[通管局审核] 1&#xff0c;在小程序后台找到备案入口 &#xff08;1&#xff09;新的未上架小程序&#xff0c;可以在小程序首页点击【去备案】进入。 &#xff08;2&#xff09;已上架小程…

交换机端口镜像技术原理与配置

在网络维护的过程中会遇到需要对报文进行获取和分析的情况&#xff0c;比如怀疑有攻击报文&#xff0c;此时需要在不影响报文转发的情况下&#xff0c;对报文进行获取和分析。镜像技术可以在不影响报文正常处理流程的情况下&#xff0c;将镜像端口的报文复制一份到观察端口&…

基于Java版本与鸿鹄企业电子招投标系统的二次开发实践-鸿鹄企业电子招投标系统源代码+支持二开+鸿鹄电子招投标系统

随着市场竞争的加剧和企业规模的扩大&#xff0c;招采管理逐渐成为企业核心竞争力的重要组成部分。为了提高招采工作的效率和质量&#xff0c;我们提出了一种基于电子化平台的解决方案。该方案旨在通过电子化招投标&#xff0c;使得招标采购的质量更高、速度更快&#xff0c;同…

L1-061:新胖子公式

题目描述 根据钱江晚报官方微博的报导&#xff0c;最新的肥胖计算方法为&#xff1a;体重(kg) / 身高(m) 的平方。如果超过 25&#xff0c;你就是胖子。于是本题就请你编写程序自动判断一个人到底算不算胖子。 输入格式&#xff1a; 输入在一行中给出两个正数&#xff0c;依次为…

ImageJ图像滤波基础

文章目录 滤波简单滤波器卷积滤波Unsharp MaskTop Hat ImageJ系列&#xff1a;安装与初步&#x1f48e;灰度图像处理 滤波 预设滤波器 ImageJ的Process菜单提供了诸多图像滤波器&#xff0c;其中大部分方法均可定制参数&#xff0c;但也提供了一些已经预设参数的处理方法。 …

博易大师智星系统外盘资管系统的功能介绍!

1. 市场行情数据接收和显示&#xff1a;软件需要接收实时的市场行情数据&#xff0c;并将其以图形或数字的形式显示出来&#xff0c;包括价格、成交量、成交额等信息。 2. 交易操作界面&#xff1a;软件需要提供一个交易操作界面&#xff0c;供用户进行交易操作&#xff0c;包括…

少走十年弯路!!!webpack详解

webpack是什么&#xff1f;&#xff1f; 本质上&#xff0c;webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时&#xff0c;它会在内部从一个或多个入口点构建一个 依赖图(dependency graph)&#xff0c;然后将你项目中所需的每一个模…

sql_lab之sqli中的搜索型注入

搜索型注入 原理是运用模糊查询&#xff1a; select * from users where username like %a% 1.找到具有模糊查询的搜索框的注入点 2.构造闭合 因为模糊查询的代码是 select * from users where username like %a% 所以应该 鱼%’ -- s 判断构造闭合的函数是否正确 鱼%…

Spring Boot3 Web开发技术

前期回顾 springboot项目常见的配置文件类型有哪些&#xff1f;哪种类型的优先级最高 yml properties yaml 读取配置文件里的数据用什么注解&#xff1f; value restful风格 RESTful 风格与传统的 HTTP 请求方式相比&#xff0c;更加简洁&#xff0c;安全&#xff0c;能隐…

成为小leader后,最大的感受就是:领导真的更偏爱主动汇报的下属!

* 你好&#xff0c;我是前端队长&#xff0c;在职场&#xff0c;玩副业&#xff0c;文末有福利! 有些朋友在职场&#xff0c;可能会陷入一个误区&#xff0c;觉得我们的付出&#xff0c;领导都能看到。这就大错特错了&#xff0c;很简单&#xff0c;打个比方&#xff0c;你有10…

MySQL数据库——InnoDB引擎-逻辑存储结构(表空间、段、区、页、行)

目录 表空间 段 区 页 行 之前我们初步介绍过InnoDB引擎的逻辑存储结构&#xff0c;如下图所示&#xff1a; 下面来对其每个组成部分详细了解&#xff1a; 表空间 表空间是InnoDB存储引擎逻辑结构的最高层&#xff0c; 如果用户启用了参数 innodb_file_per_table(在 …

Python - 深夜数据结构与算法之 Divide Conquer Backtrack

目录 一.引言 二.分治与回溯简介 1.Divide & Conquer 分治 2.BackTrack 回溯 三.经典算法实战 1.Combination-Of-Phone [17] 2.Permutations [46] 3.Permutations-2 [47] 4.Pow-X [50] 5.N-Queen [51] 6.Combinations [78] 7.Sub-Sets [78] 8.Majority-Elemen…

如何修复无法读取的U盘,修复U盘的方法

无法读取U盘是常见的故障&#xff0c;可能的原因有很多&#xff0c;例如U盘驱动器问题、文件系统损坏、电脑USB接口问题等。本文将详细分析这些原因&#xff0c;并提供相应的解决方法&#xff0c;帮助用户解决无法读取U盘的问题。 如何修复无法读取的U盘&#xff0c;修复U盘的方…

Python - 深夜数据结构与算法之 Recursion

目录 一.引言 二.递归的简介 1.Recursion 递归 2.Factorial 阶乘 3.Template 模版 三.经典算法实战 1.Generate-Parentheses [22] 2.Climbing-Stairs [70] 3.Is-Valid-BST [98] 4.Max-Depth [104] 5.Construct-Binary-Tree [105] 6.Min-Depth [111] 7.Invert-Tree…

DML语言(重点)———update

格式&#xff1a;update 要修改的对象 set 原来的值新值 -- 修改学员名字,带了简介 代码案例&#xff1a; -- 修改学员名字,带了简介 UPDATE student SET name清宸 WHERE id 1; -- 不指定条件情况下&#xff0c;会改动所有表&#xff01; 代码案例…

@click 默认传递原生的事件对象

项目场景 [Day1] <template><div id"app"><h1>小黑记事本</h1><button click"handleClick">www</button><div class"head"><!-- 按键&#xff08;回车&#xff09;按下&#xff0c;出发add事件&…

适合引流源码声音鉴定神器网站源码,轻松吸引用户关注

源码介绍 声鉴卡HTML5网页源码&#xff0c;完整可运转&#xff0c;调用wx录音&#xff0c;自动判断声音属性&#xff0c;输出结果 安装教程 只需要把源码上传至主机空间就可以 支持上传二级目录访问&#xff01;提示一下&#xff1a;wxvx打开效果是最佳的源码里面生成二维码…

TIA博途中临时变量Temp的基本概念解析

TIA博途中临时变量Temp的基本概念解析 TEMP&#xff08;临时变量&#xff09;的概念是这样的。 每一个OB&#xff0c;FB&#xff0c;FC都可以声明Temp临时变量。 它们仅是在被调用时&#xff0c;由操作系统&#xff0c;根据块声明&#xff0c;临时分配的内存空间。该块结束时…

RTOS_WDS

2023/12/25重启韦东山老师RTO 韦东山freeRTOS快速入门视频教程 P2 2-1堆的概念 堆 char heap_buf[1024]; int pos 0;void *my_malloc(int size) {int old_pos pos;pos size;return &heap_buf[old_pos]; }void my_free(void *buf) {/* err */ }int main(void) {char ch…

Linux操作系统基础知识点

Linux是一种计算机操作系统&#xff0c;其内核由林纳斯本纳第克特托瓦兹&#xff08;Linus Benedict Torvalds&#xff09;于1991年首次发布。Linux操作系统通常与GNU套件一起使用&#xff0c;因此也被称为GNU/Linux。它是一种类UNIX的操作系统&#xff0c;设计为多用户、多任务…