【Jetpack】使用 Room 框架访问 Android 平台 SQLite 数据库 ( 导入依赖 | 定义 Entity 实体类 | 定义 Dao 数据库访问对象接口 | 定义数据库实例类 )

2023-10-11 16:49:21 浏览数 (1)

一、Room 框架使用核心要点

1、build.gradle 构建脚本配置说明

对于 Room 框架 来说 , 使用 Java 语言 开发和使用 Kotlin 语言 开发 , 需要在 build.gradle 构建脚本 中进行不同的配置 , 主要有以下两个配置不同 :

  • 应用的插件不同 ;
  • 导入依赖库方式不同 ;
应用插件

应用的插件区别 :

  • 如果使用 Java 语言开发 , 只需要导入 android 插件 ;
代码语言:javascript复制
plugins {
    id 'com.android.application'
}
  • 如果使用 Kotlin 语言开发 , 需要在 android 插件 的基础上 额外导入 kotlin 和 kotlin-kapt 插件 ;
代码语言:javascript复制
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
}
导入依赖

导入依赖库区别 : 如果导入错误 , 就会出现

  • 如果使用 Java 语言开发 , 需要使用 annotationProcessor 导入注解处理器 , 用于在编译期间生成代码 ;
代码语言:javascript复制
    // 导入 Room 依赖库
    implementation 'androidx.room:room-runtime:2.2.5'
    // 导入注解处理器 ( Java )
    annotationProcessor 'androidx.room:room-compiler:2.2.5'
  • 如果使用 Kotlin 语言开发 , 需要使用 kapt 导入注解处理器 , 用于在编译期间生成代码 ;
代码语言:javascript复制
    // 导入 Room 依赖库
    implementation 'androidx.room:room-runtime:2.2.5'
    // 导入注解处理器 ( Kotlin )
    kapt 'androidx.room:room-compiler:2.2.5'

2、定义 Entity 实体类

使用 Room 框架 的应用中 , Entity 实体类 对应着 数据库表 , 将 Entity 实体类 定义完成后 , 就意味着 数据库表的结构 已经定义完成 ;

@Entity 注解

Entity 实体类 需要使用 @Entity 注解进行修饰 , 该注解可以 传入 tableName 参数 , 该 tableName 参数的作用是定义数据库表的名称 ;

代码语言:javascript复制
@Entity(tableName = "student")
class Student {
}
@PrimaryKey 注解

定义主键 , 使用 @PrimaryKey 注解修饰主键 , 设置 autoGenerate = true 参数 可以令 主键自增 ;

数据库表 列信息 使用 @ColumnInfo 注解定义 , 该注解中的参数 :

  • name = “id” 定义了 数据表的列名称 ;
  • typeAffinity = ColumnInfo.INTEGER 定义该 列字段的类型是 int 整型 ;
代码语言:javascript复制
    /**
     * @PrimaryKey 设置主键 autoGenerate 为自增
     * @ColumnInfo name 设置列名称 / typeAffinity 设置列类型
     */
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
    var id: Int = 0
@ColumnInfo 注解

定义普通字段 , 只需要使用 @ColumnInfo 注解修饰字段即可 , 通过 name = "name" 设置数据库表名称 , typeAffinity = ColumnInfo.TEXT 设置该列的类型为 String 类型 ;

代码语言:javascript复制
    /**
     * 姓名字段
     * 数据库表中的列名为 name
     * 数据库表中的类型为 TEXT 文本类型
     */
    @ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)
    lateinit var name: String
@Ignore 注解
修饰字段

如果 有些字段 不需要设置为数据库表列 , 仅用于业务逻辑中使用 , 不需要插入数据库中 , 使用 @Ignore 修饰该字段即可 ;

代码语言:javascript复制
    /**
     * 有些属性用于做业务逻辑
     * 不需要插入到数据库中
     * 使用 @Ignore 注解修饰该属性字段
     */
    @Ignore
    lateinit var studentInfo: String
修饰函数

使用 @Ignore 注解标注构造函数后 , Room 框架就不会使用该构造方法了 ;

下面的 3 个构造函数中 ,

  • Room 框架 使用 constructor(id: Int, name: String, age: Int) 构造函数 ;
  • 开发者 使用 constructor(name: String, age: Int)constructor(id: Int) 构造函数 ;
代码语言:javascript复制
    /**
     * 默认的构造方法给 Room 框架使用
     */
    constructor(id: Int, name: String, age: Int) {
        this.id = id
        this.name = name
        this.age = age
    }

    /**
     * 使用 @Ignore 注解标注构造函数后
     * Room 就不会使用该构造方法了
     * 这个构造方法是给开发者使用的
     */
    @Ignore
    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }

    /**
     * 使用 @Ignore 标签标注后
     * Room 就不会使用该构造方法了
     * 这个构造方法是给开发者使用的
     */
    @Ignore
    constructor(id: Int) {
        this.id = id
    }

3、定义 Dao 数据库访问对象接口

@Dao 注解

定义的 Dao 数据库访问对象接口 是一个 interface 接口 , 使用 @Dao 注解修饰该接口 ;

代码语言:javascript复制
/**
 * 数据库访问对象接口 / 使用 @Dao 注解修饰
 * 提供数据库的增删改查方法
 */
@Dao
interface StudentDao {
@Insert 注解

向数据库中插入数据 , 使用 @Insert 注解修饰对应的抽象方法 ;

代码语言:javascript复制
    /**
     * 向数据库表中插入元素
     */
    @Insert
    fun insert(student: Student)
@Delete 注解

从数据库中删除数据 , 使用 @Delete 注解修饰对应的抽象方法 ;

代码语言:javascript复制
    /**
     * 从数据库表中删除元素
     */
    @Delete
    fun delete(student: Student)
@Update 注解

更新数据库中的数据 , 使用 @Update 注解修饰对应的抽象方法 ;

代码语言:javascript复制
    /**
     * 修改数据库表元素
     */
    @Update
    fun update(student: Student)
@Query 注解

查询数据库中的数据 , 使用 @Query 注解修饰对应的抽象方法 ;

注解中可以设置字符串参数 , 该字符串参数就是查询的 SQL 语句 , 使用 冒号 : 可访问传入的参数 ;

代码语言:javascript复制
    /**
     * 查询数据库表
     */
    @Query("select * from student")
    fun query(): List<Student>

    /**
     * 根据传入的 id 查询数据库表
     * 在注解中使用 :id 调用参数中的 id: Int
     */
    @Query("select * from student where id = :id")
    fun query(id: Int): List<Student>

4、定义 RoomDatabase 数据库实例类

定义的 RoomDatabase 数据库实例类 是一个 抽象类 , 需要继承 RoomDatabase 抽象类 , 同时要使用 @Database 注解修饰 ,

代码语言:javascript复制
@Database(entities = [Student::class], version = 1, exportSchema = false)
abstract class StudentDatabase: RoomDatabase() {

在该抽象类中定义抽象方法 , 获取 数据库访问 对象 ,

代码语言:javascript复制
    /**
     * 获取 数据库访问 对象
     * 这是必须要实现的函数
     */
    abstract fun studentDao(): StudentDao

将该类设置成单例类 , 在单例类对象初始化时 , 创建数据库 ;

代码语言:javascript复制
    companion object {
        lateinit var instance: StudentDatabase

        fun inst(context: Context): StudentDatabase {
            if (!::instance.isInitialized) {
                synchronized(StudentDatabase::class) {
                    // 创建数据库
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        StudentDatabase::class.java,
                        "student_database.db")
                        .allowMainThreadQueries() // Room 原则上不允许在主线程操作数据库
                                                  // 如果要在主线程操作数据库需要调用该函数
                        .build()
                }
            }
            return instance;
        }
    }

初始化数据库代码 :

  • 首先 , 调用 Room.databaseBuilder 函数 , 创建 RoomDatabase.Builder 对象 , 传入 Context 上下文 , StudentDatabase 类对象 , 数据库名称 等参数 ;
  • 然后 , 调用 RoomDatabase.Builder 对象的 build 函数 , 创建数据库 ;
  • 注意 : Room 原则上不允许在主线程操作数据库 , 如果要在主线程操作数据库 需要调用 RoomDatabase.Builder # allowMainThreadQueries 函数 , 允许主线程操作 ;
代码语言:javascript复制
                    // 创建数据库
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        StudentDatabase::class.java,
                        "student_database.db")
                        .allowMainThreadQueries() // Room 原则上不允许在主线程操作数据库
                                                  // 如果要在主线程操作数据库需要调用该函数
                        .build()

5、调用 Room 框架访问数据库

首先 , 获取 RoomDatabase 数据库实例类 , 调用其单例的获取函数即可 , 调用该函数 , 即可创建对应的数据库 ;

代码语言:javascript复制
        // 获取 StudentDatabase
        var studentDatabase: StudentDatabase = StudentDatabase.inst(this)

然后 , 获取 数据库访问对象 Dao , 通过 RoomDatabase 数据库实例类 的抽象方法获取 ;

代码语言:javascript复制
        // 获取数据库访问对象
        var studentDao: StudentDao = studentDatabase.studentDao()

最后 , 使用 Dao 数据库访问对象 , 进行数据库访问操作 , 推荐在线程中访问数据库 ;

代码语言:javascript复制
        thread(start = true) {
            // 插入数据
            var s1 = Student("Tom", 18)
            var s2 = Student("Jerry", 16)
            studentDao.insert(s1)
            studentDao.insert(s2)

            // 查询数据
            var students = studentDao.query()
            Log.i("MainActivity", "数据库查询结果 ( 插入后首次查询 ) : "   students)

            // 更新数据 , 将学生年龄都设置为 20
            for (i in 0.. students.size - 1) {
                students[i].age = 20
                studentDao.update(students[i])
            }
            students = studentDao.query()
            Log.i("MainActivity", "数据库查询结果 ( 修改后查询结果 ) : "   students)

            // 删除数据
            var s_delete = Student(1)   // 删除的元素只需要传入 id 即可
            studentDao.delete(s_delete)
            students = studentDao.query()
            Log.i("MainActivity", "数据库查询结果 ( 删除后查询结果 ) : "   students)
        }

二、完整代码示例


1、build.gradle 构建脚本

在 build.gradle 构建脚本中 , 主要是导入 Kotlin 插件 , 和 Kotlin 注解插件 ;

如果使用 Java 语言开发 , 则不需要导入这两个插件 ;

代码语言:javascript复制
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'

导入 Room 依赖库 和 编译时生成代码的 注解处理器 ;

代码语言:javascript复制
    // 导入 Room 依赖库
    implementation 'androidx.room:room-runtime:2.2.5'
    // 导入注解处理器 ( Kotlin )
    kapt 'androidx.room:room-compiler:2.2.5'
    // 导入注解处理器 ( Java )
    //annotationProcessor 'androidx.room:room-compiler:2.2.5'

完整代码如下 :

代码语言:javascript复制
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
}

android {
    namespace 'kim.hsl.roomdemo'
    compileSdk 32

    defaultConfig {
        applicationId "kim.hsl.roomdemo"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }

    viewBinding {
        // 启用 ViewBinding
        enabled = true
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    // 导入 Room 依赖库
    implementation 'androidx.room:room-runtime:2.2.5'
    // 导入注解处理器 ( Kotlin )
    kapt 'androidx.room:room-compiler:2.2.5'
    // 导入注解处理器 ( Java )
    //annotationProcessor 'androidx.room:room-compiler:2.2.5'
}

2、Entity 实体类代码

代码语言:javascript复制
package kim.hsl.roomdemo

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey

/**
 * 定义数据库表 Entity 实体 / 同时定义数据库表 和 对鹰的实体类
 * 设置该数据类对应数据库中的一张数据表, 表名为 student
 * 该数据库表中的数据对应一个 Student 类实例对象
 */
@Entity(tableName = "student")
class Student {
    /**
     * @PrimaryKey 设置主键 autoGenerate 为自增
     * @ColumnInfo name 设置列名称 / typeAffinity 设置列类型
     */
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
    var id: Int = 0

    /**
     * 姓名字段
     * 数据库表中的列名为 name
     * 数据库表中的类型为 TEXT 文本类型
     */
    @ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)
    lateinit var name: String

    /**
     * 年龄字段
     * 数据库表中的列名为 age
     * 数据库表中的类型为 INTEGER 文本类型
     */
    @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
    var age: Int = 0

    /**
     * 有些属性用于做业务逻辑
     * 不需要插入到数据库中
     * 使用 @Ignore 注解修饰该属性字段
     */
    @Ignore
    lateinit var studentInfo: String

    /**
     * 默认的构造方法给 Room 框架使用
     */
    constructor(id: Int, name: String, age: Int) {
        this.id = id
        this.name = name
        this.age = age
    }

    /**
     * 使用 @Ignore 注解标注构造函数后
     * Room 就不会使用该构造方法了
     * 这个构造方法是给开发者使用的
     */
    @Ignore
    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }

    /**
     * 使用 @Ignore 标签标注后
     * Room 就不会使用该构造方法了
     * 这个构造方法是给开发者使用的
     */
    @Ignore
    constructor(id: Int) {
        this.id = id
    }

    override fun toString(): String {
        return "Student(id=$id, name='$name', age=$age)"
    }
}

3、Dao 数据库访问对象接口代码

使用 @Dao 注解修饰接口类 ;

分别使用 @Insert , @Delete , @Update , @Query 注解 修饰对应的 增加 , 删除 , 修改 , 查询 等 函数 ;

完整代码 :

代码语言:javascript复制
package kim.hsl.roomdemo

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update

/**
 * 数据库访问对象接口 / 使用 @Dao 注解修饰
 * 提供数据库的增删改查方法
 */
@Dao
interface StudentDao {
    /**
     * 向数据库表中插入元素
     */
    @Insert
    fun insert(student: Student)

    /**
     * 从数据库表中删除元素
     */
    @Delete
    fun delete(student: Student)

    /**
     * 修改数据库表元素
     */
    @Update
    fun update(student: Student)

    /**
     * 查询数据库表
     */
    @Query("select * from student")
    fun query(): List<Student>

    /**
     * 根据传入的 id 查询数据库表
     * 在注解中使用 :id 调用参数中的 id: Int
     */
    @Query("select * from student where id = :id")
    fun query(id: Int): List<Student>
}

4、RoomDatabase 数据库实例类代码

RoomDatabase 数据库实例类 需要继承 RoomDatabase 抽象类 , 使用 @Database 注解修饰 该抽象类 ;

其中需要 定义 获取 数据库访问 对象 的抽象函数 ;

将该类定义成 单例类 , 在单例对象初始化时 , 创建数据库 ;

完整代码 :

代码语言:javascript复制
package kim.hsl.roomdemo

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(entities = [Student::class], version = 1, exportSchema = false)
abstract class StudentDatabase: RoomDatabase() {
    /**
     * 获取 数据库访问 对象
     * 这是必须要实现的函数
     */
    abstract fun studentDao(): StudentDao

    companion object {
        lateinit var instance: StudentDatabase

        fun inst(context: Context): StudentDatabase {
            if (!::instance.isInitialized) {
                synchronized(StudentDatabase::class) {
                    // 创建数据库
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        StudentDatabase::class.java,
                        "student_database.db")
                        .allowMainThreadQueries() // Room 原则上不允许在主线程操作数据库
                                                  // 如果要在主线程操作数据库需要调用该函数
                        .build()
                }
            }
            return instance;
        }
    }
}

5、在 Activity 组件中通过调用 Room 框架访问数据库

首先 , 获取 数据库实例类 StudentDatabase ;

然后 , 获取 数据库访问对象接口 StudentDao ;

最后 , 调用 数据库访问对象接口 StudentDao 的一系列方法访问数据库 ;

完整代码 :

代码语言:javascript复制
package kim.hsl.roomdemo

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kim.hsl.roomdemo.databinding.ActivityMainBinding
import kotlin.concurrent.thread

class MainActivity : AppCompatActivity() {
    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(getLayoutInflater())
        setContentView(binding.root)

        // 获取 StudentDatabase
        var studentDatabase: StudentDatabase = StudentDatabase.inst(this)

        // 获取数据库访问对象
        var studentDao: StudentDao = studentDatabase.studentDao()

        thread(start = true) {
            // 插入数据
            var s1 = Student("Tom", 18)
            var s2 = Student("Jerry", 16)
            studentDao.insert(s1)
            studentDao.insert(s2)

            // 查询数据
            var students = studentDao.query()
            Log.i("MainActivity", "数据库查询结果 ( 插入后首次查询 ) : "   students)

            // 更新数据 , 将学生年龄都设置为 20
            for (i in 0.. students.size - 1) {
                students[i].age = 20
                studentDao.update(students[i])
            }
            students = studentDao.query()
            Log.i("MainActivity", "数据库查询结果 ( 修改后查询结果 ) : "   students)

            // 删除数据
            var s_delete = Student(1)   // 删除的元素只需要传入 id 即可
            studentDao.delete(s_delete)
            students = studentDao.query()
            Log.i("MainActivity", "数据库查询结果 ( 删除后查询结果 ) : "   students)
        }


    }
}

0 人点赞