117.使用Room进行增删改查
注意:在实际项目中,你可能会基于 ViewModel 和 Repository 架构来实现数据操作,这里为了简单演示,将 CRUD 操作直接放在 Activity 中。下面是一份详细的教程,示例展示了如何使用 Room 完成增、删、改、查(CRUD)操作。Room 通过注解处理器在编译时生成大量的样板代码,减少了手动编写 SQL 代码的需要,提高了开发效率和代码可维护性。注解的 Kotlin 或
Room persistence library 是 Android Jetpack 提供的一个 SQLite 对象映射 (ORM) 库。它允许你以更简单、更方便的方式访问和管理 SQLite 数据库。
核心概念:
-
数据库 (Database): 使用
@Database注解的抽象类,继承自RoomDatabase。它是数据库的入口点,包含数据库版本信息和 DAO 接口列表。Room 会自动生成该类的实现。 -
实体 (Entity): 使用
@Entity注解的 Kotlin 或 Java 类,代表数据库中的一个表。类的每个字段对应表中的一列。 可以定义主键、索引等。 -
数据访问对象 (DAO): 使用
@Dao注解的接口或抽象类,定义了访问数据库的方法。使用@Query、@Insert、@Update、@Delete等注解将方法与 SQL 查询关联起来。Room 会生成 DAO 的实现。
优点:
- 编译时验证: Room 会在编译时验证 SQL 查询的正确性,避免运行时错误。
- 简化数据库操作: Room 提供了简单的 API 来执行数据库操作,无需编写复杂的 SQL 语句。
- 与协程集成: Room 支持 Kotlin 协程,可以方便地在后台线程中执行数据库操作。
- 数据库迁移: Room 提供了数据库迁移机制,可以平滑地升级数据库结构。
- 与 Android Jetpack 集成: Room 是 Android Jetpack 的一部分,与 Android 平台的其他组件无缝集成。
Room 通过注解处理器在编译时生成大量的样板代码,减少了手动编写 SQL 代码的需要,提高了开发效率和代码可维护性。它是一个强大而灵活的数据库解决方案,适用于大多数 Android 应用程序。
下面是一份详细的教程,示例展示了如何使用 Room 完成增、删、改、查(CRUD)操作。本文将带你一步步创建实体类、DAO 接口、Database 类,并在 Activity 中调用这些方法实现数据操作。
下面的示例基于 Kotlin,并使用协程在后台线程执行数据库操作。注意:在实际项目中,你可能会基于 ViewModel 和 Repository 架构来实现数据操作,这里为了简单演示,将 CRUD 操作直接放在 Activity 中。
────────────────────────────
- 创建实体类
创建一个数据类(实体),用于表示数据库中的一张表。例如,这里我们创建一个 User 表,用于存储用户信息: - User.kt:
package com.example.myapplication
import androidx.room.Entity
import androidx.room.PrimaryKey
/**
* 数据实体类,代表数据库中的 "users" 表。
*/
/*
Entity: 这个注解告诉 Room,被它标记的 Kotlin 类应该被视为一个数据库表。换句话说,Room 会根据这个类的结构来创建数据库表。
tableName = "users": 这个参数指定了数据库表的名称。在这里,表名被设置为 "users"
如果不指定 tableName,Room 默认使用类名作为表名(在本例中就是 "User")。
关键字 data 表明这是一个数据类 ,会自动生成一下方法
@PrimaryKey 注解放在 id 属性上,表示 id 是 users 表的主键。
主键(Primary Key)是一种关系型数据库中用于唯一标识表中每一条记录的字段或字段组合。
当 autoGenerate = true 时,Room 会让数据库(通常是 SQLite)负责为新插入的行自动生成唯一的主键值。
工作原理:
自增: 数据库会自动为每个新插入的行分配一个唯一的、递增的整数值作为主键。这意味着每个新行的主键值都会比前一个行的主键值大 1
*/
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val name: String,
val age: Int
)
- 创建 DAO 接口
DAO(Data Access Object)定义了访问数据库中 User 表的方法,包括增删改查操作。下面的代码演示了常用的 CRUD 方法:
UserDao.kt:
package com.example.myapp
import androidx.room.*
@Dao
interface UserDao {
// 插入用户,返回新用户 ID
@Insert
suspend fun insertUser(user: User): Long
// 更新用户信息
@Update
suspend fun updateUser(user: User)
// 删除用户
@Delete
suspend fun deleteUser(user: User)
// 查询所有用户
@Query("SELECT * FROM users")
suspend fun getAllUsers(): List<User>
// 根据 ID 查询用户
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User?
}
- 创建 Room 数据库
这里创建一个继承自 RoomDatabase 的抽象类,绑定上一步的实体类和 DAO。我们还通过单例模式,保证整个应用中只创建一个数据库实例:
AppDatabase.kt:
package com.example.myapplication
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
/**
* 定义 Room 数据库,包含 User 表以及对应的 DAO。
*/
/*
@Database 是一个类级别的注解,用于定义 Room 数据库。
它告诉 Room 这个类是数据库的入口点,并包含数据库的配置信息。
entities: 这个参数用于指定数据库包含的实体类(entity classes)。实体类代表数据库中的表。
User::class 表示 User 类是一个实体类,对应于数据库中的一个表。
version: 这个参数用于指定数据库的版本号。版本号是一个整数,用于跟踪数据库的结构变化。
exportSchema: 这个参数用于指定是否将数据库的 schema 导出到文件中
Schema 是数据库的结构描述,包括表名、列名、数据类型等信息。
false: 表示不导出数据库的 schema。如果设置为 true,Room 会将 schema 导出到 JSON 文件中,方便你进行版本控制和代码审查
但是,在生产环境中,通常不需要导出 schema,因此可以设置为 false。
抽象类不能被直接实例化,必须被其他类继承。
RoomDatabase 是 Room 持久化库中的一个抽象类,用于定义 Room 数据库
AppDatabase 类继承自 RoomDatabase,这意味着它是一个 Room 数据库。
*/
@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
// 确保数据库类在整个应用中只有一个实例,使用单例模式
/*
在多线程编程中,如果没有 volatile 关键字,线程可能会从自己的 CPU 缓存中读取变量的值,而不是从主内存中读取
这会导致线程之间的数据不一致性。@Volatile 关键字可以解决这个问题,确保所有线程都能看到变量的最新值。
synchronized(this): 这是一个同步块,用于在多线程环境下保证线程安全
synchronized 关键字确保在同一时刻只有一个线程可以执行同步块中的代码。
Room.databaseBuilder 用于创建一个数据库构建器
你需要通过该构建器指定数据库的上下文、数据库类和数据库名称,然后调用 build() 方法来创建数据库实例。
{ INSTANCE = it } 是一个 lambda 表达式,它接受一个参数 it,it 指的是 build() 函数返回的 RoomDatabase 实例。
INSTANCE = it 将 build() 函数返回的 RoomDatabase 实例赋值给 INSTANCE 变量
INSTANCE 通常是一个 companion object 中的静态变量,用于保存单例实例。
*/
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE ?: Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
).build().also { INSTANCE = it }
}
}
}
- 在 Activity 中使用 Room 执行 CRUD 操作
下面演示了如何在 Activity 中获取数据库实例、调用 DAO 方法,利用协程在后台线程中进行这些操作,并打印日志显示操作结果:
Main.kt:
package com.example.myapplication
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
/**
* MainActivity 演示如何调用 Room 的 CRUD 方法。
*/
class MainActivity : AppCompatActivity() {
private lateinit var db: AppDatabase
private lateinit var userDao: UserDao
private val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 请确保 res/layout/activity_main.xml 文件存在,这里只使用简单的布局
setContentView(R.layout.activity_main)
// 获取数据库实例和 UserDao 对象
db = AppDatabase.getDatabase(this)
userDao = db.userDao()
// 在后台线程中调用数据库操作,避免阻塞主线程
/*
CoroutineScope: 创建一个协程作用域,用于启动和管理协程。
Dispatchers.IO: 指定协程的调度器为 Dispatchers.IO。Dispatchers.IO 调度器专门用于执行 I/O 密集型任务
例如数据库操作、网络请求等。它使用一个线程池来执行任务,以避免阻塞主线程。
launch: 是 CoroutineScope 的一个扩展函数,用于启动一个新的协程。
这是一个函数,用于执行数据库的 CRUD 操作。通常,这个函数会包含对 Room 数据库的插入、查询、更新和删除操作。
*/
CoroutineScope(Dispatchers.IO).launch {
performCrudOperations()
}
}
/**
* 执行增加、查询、更新、删除操作,并通过日志打印每一步的结果。
*/
private suspend fun performCrudOperations() {
try {
// 1. 增:插入用户
val userId = userDao.insertUser(User(name = "Alice", age = 28))
Log.d(TAG, "Inserted user with ID: $userId")
// 2. 查:获取所有用户
val userList = userDao.getAllUsers()
Log.d(TAG, "All users: $userList")
// 3. 查:通过 ID 获取用户
val userFromDB = userDao.getUserById(userId.toInt())
Log.d(TAG, "Fetched user: $userFromDB")
// 4. 改:更新用户信息(此处先确认用户存在)
userFromDB?.let { user ->
val updatedUser = user.copy(name = "Alice Updated", age = 29)
userDao.updateUser(updatedUser)
Log.d(TAG, "Updated user: $updatedUser")
// 验证更新
val confirmedUser = userDao.getUserById(userId.toInt())
Log.d(TAG, "Confirmed updated user: $confirmedUser")
}
// 5. 删:删除用户
userFromDB?.let { user ->
userDao.deleteUser(user)
Log.d(TAG, "Deleted user: $user")
}
// 6. 再次查询以验证删除结果
val finalUsers = userDao.getAllUsers()
Log.d(TAG, "Final users list: $finalUsers")
} catch (e: Exception) {
Log.e(TAG, "Error performing CRUD operations", e)
}
}
}
gradle:
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
// 确保导入了 kapt 插件
id("org.jetbrains.kotlin.kapt")
}
android {
namespace = "com.example.myapplication"
compileSdk = 35
defaultConfig {
applicationId = "com.example.myapplication"
minSdk = 24
targetSdk = 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
}
dependencies {
// 添加 Room 依赖项
implementation("androidx.room:room-runtime:2.5.1")
implementation("androidx.room:room-ktx:2.5.1")
kapt("androidx.room:room-compiler:2.5.1")
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
总结:
-
使用 Room 实现 CRUD 的主要步骤包括:
- 创建实体类(Entity)用于定义数据库中的表结构;
- 定义 DAO 接口,声明增删改查方法;
- 创建继承 RoomDatabase 的数据库类,并获取单例实例;
- 在业务层(如 Activity、ViewModel 等)中使用 DAO 来进行数据库操作。
-
注意事项:
- 数据库操作需要在后台线程中执行,避免阻塞主线程;
- Room 与 Kotlin 协程搭配使用时,需要保证 DAO 中的 suspend 函数在协程内调用;
- 在实际项目中,建议将数据库操作放在 Repository 层,通过 ViewModel 层与 UI 进行交互,从而实现分层结构和更好的可维护性。
更多推荐


所有评论(0)