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

news2025/2/4 15:42:17

一、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_1_17
        targetCompatibility = JavaVersion.VERSION_1_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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[前端优化]项目优化--Lighthouse

[前端优化]项目优化--Lighthouse 前端优化的分类Lighthouse 优化工具优化维度--性能(Performance)性能指标概览白屏时间--FP首字节时间--TTFB首次输入延迟--FID累积布局偏移--CLS 性能指标分析Lighthouse的性能优化方案性能优化实战解析Serve images in next-gen formatsEnable…

Postman报:400 Bad Request

● 使用Postman发送Post请求报400&#xff0c;入参为JSON&#xff1b; 二、分析 1、Postman请求并没有请求到后台Api&#xff08;由于语法错误&#xff0c;服务器无法理解请求&#xff09;&#xff1b; 2、入参出错范围&#xff1a;cookie、header、body、form-data、x-www-f…

C# Onnx yolov8n csgo player detection

目录 效果 模型信息 项目 代码 下载 C# Onnx yolov8n csgo player detection 效果 模型信息 Model Properties ------------------------- date&#xff1a;2023-12-22T15:01:08.014205 author&#xff1a;Ultralytics task&#xff1a;detect license&#xff1a;AGPL-…

Open5GSUeRANSim3:VirtualBOX VM使用static IP并和host互通

本文档参考 https://blog.csdn.net/shuaihj/article/details/127589833 https://www.cnblogs.com/manongqingcong/articles/16659150.html https://blog.csdn.net/justlpf/article/details/132977047 VM默认使用的是自动分配的IP&#xff0c;每个VM的ip都是10.0.2.15。后续为了…

管理类联考——数学——真题篇——按知识分类——代数——数列

【等差数列 ⟹ \Longrightarrow ⟹ 通项公式&#xff1a; a n a 1 ( n − 1 ) d a m ( n − m ) d n d a 1 − d A n B a_n a_1(n-1)d a_m(n-m)dnda_1-dAnB an​a1​(n−1)dam​(n−m)dnda1​−dAnB ⟹ \Longrightarrow ⟹ A d &#xff0c; B a 1 − d Ad&#x…

HBuilderX项目配置使用uview

配置uview&#xff0c;先安装再配置 如果没有package.json文件&#xff0c;先打开终端&#xff0c;执行命令 npm init -y 然后就会生成 package.json 安装 使用npm安装uview npm install uview-ui2.0.36 安装好之后&#xff0c;可以看到package.json里面已经显示版本了 查…

中间件系列 - Redis入门到实战(实战篇)

前言 学习视频&#xff1a; 黑马程序员Redis入门到实战教程&#xff0c;深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目 本内容仅用于个人学习笔记&#xff0c;如有侵扰&#xff0c;联系删除 本章学习目标&#xff1a; 短信登录 这一块我们会使用redis…

Java_集合进阶Map实现类

一、Map集合 已经学习了Map集合的常用方法&#xff0c;以及遍历方式。 下面学习的是Map接口下面的是三个实现类HashMap、LinkedHashMap、TreeMap。实际上这三个实现类并没有什么特有方法需要我们学习&#xff0c;它们的方法就是前面学习Map的方法。这里我们主要学习它们的底层…

由浅入深走进Python异步编程【多进程】(含代码实例讲解 || multiprocessing、异步进程池、进程通信)

写在前面 从底层到第三方库&#xff0c;全面讲解python的异步编程。这节讲述的是python的多线程实现&#xff0c;纯干货&#xff0c;无概念&#xff0c;代码实例讲解。 本系列有6章左右&#xff0c;点击头像或者专栏查看更多内容&#xff0c;陆续更新&#xff0c;欢迎关注。 …

JavaWeb笔记之前端开发JQuery

一、引言 1.1 概述 jQuery是一个快速、简洁的JavaScript代码库。jQuery设计的宗旨是“Write Less&#xff0c;Do More”&#xff0c;即倡导写更少的代码&#xff0c;做更多的事情。它封装JavaScript常用的功能代码&#xff0c;提供一种简便的 JavaScript操作方式&#xff0c…

微信小程序格创校园跑腿小程序源码v1.1.64+前端

简介&#xff1a; 版本号&#xff1a;1.1.64 – 多学校版本 本次更新内容&#xff1a; 订单问题修复 &#xff08;无需上传小程序&#xff09; 版本号&#xff1a;1.1.63 – 多学校版本 本次更新内容&#xff1a; 失物招领增加内容安全接口&#xff1b; 认证增加性别选…

谷歌 | Duet AI 让洞察、聚类模型和可视化变得简单

迷失在数据的海洋 我们都经历过这样的情况&#xff1a;淹没在数据的海洋中&#xff0c;努力驾驭复杂的管道&#xff0c;感觉数据令人头晕。管理大量充满不同工具和 Google 搜索的选项卡以及花费大量时间筛选数据和代码以创建满足您需求的模型所带来的挫败感&#xff0c;真的会…

elasticsearch 与 mysql的概念对比

文档 elasticsearch是面向文档存储的&#xff0c;可以是数据库中的一条商品数据&#xff0c;一个订单信息。 文档数据会被序列化为json格式后存储在elasticsearch中。 索引(Index) 索引(index):相同类型的文档的集合映射(mapping):索引中文档的字段约束信息&#xff0c;类似…

SoapUI、Jmeter、Postman三种接口测试工具的比较分析!

前段时间忙于接口测试&#xff0c;也看了几款接口测试工具&#xff0c;简单从几个角度做了个比较&#xff0c;拿出来与诸位分享一下。本文从多个方面对接口测试的三款常用工具进行比较分析&#xff0c;以便于在特定的情况下选择最合适的工具&#xff0c;或者使用自己编写的工具…

开放式耳机推荐哪款好、百元最好的蓝牙耳机

说起开放式蓝牙耳机&#xff0c;相信大部分小伙伴儿都不会陌生吧。与传统的封闭式耳机相比&#xff0c;其不仅提升了佩戴的舒适性&#xff0c;而且对耳朵的保护上也非常的友好。尤其是适用于那些喜欢户外运动和长途旅行佩戴的用户。不过也因为发展的比较快&#xff0c;开放式耳…

vscode debug c++代码

需要提前写好CMakeLists.txt 在tasks.json中写好编译的步骤&#xff0c;即tasks&#xff0c;如cmake … 和make -j 在lauch.json中配置可执行文件的路径和需要执行tasks中的哪一个任务 具体步骤&#xff1a; 1.写好c代码和CMakeLists.txt 2.配置tasks.json 终端–>配置任务…

无头 SEO:技术实施的 8 个基本步骤

确保您的内容在无头 CMS 环境中大放异彩。按照我们的 8 个步骤进行一流的无头 SEO。 无头内容管理系统 &#xff08;CMS&#xff09; 正在兴起&#xff0c;迅速被宜家、耐克和国家地理等大品牌采用。 那里有很多选择&#xff0c;而且更有可能的是&#xff0c;作为 SEO 专业人士…

swing快速入门(二十三)弹球小游戏

注释很详细&#xff0c;直接上代码 上一篇 新增内容 1. 键盘响应监听 2. 使用定时器事件更新画板 3. 定时器事件的开始与暂停 4. 弹球小游戏的坐标逻辑判断 import javax.swing.*; import java.awt.*; import java.awt.event.*;public class swing_test_19 {//创建一个窗…

即将来临的2024年,汽车战场再起波澜?

我们来简要概况一下11月主流车企的销量表现&#xff1a; 根据数据显示&#xff0c;11月吉利集团总销量29.32万辆&#xff0c;同比增长28%。这在当月国内主流车企中综合实力凌厉&#xff0c;可谓表现得体。而与吉利直接竞争的比亚迪&#xff0c;尽管数据未公布&#xff0c;但我们…

gradio 基本样式

可以在下面的网页上作测试&#xff1a; Gradio PlaygroundPlay Around with Gradio Demoshttps://www.gradio.app/playground1.text、checkbox、Slider import gradio as grdef greet(name, is_morning, temperature):salutation "Good morning" if is_morning e…