当前位置: 首页 > news >正文

Jetpack Room 从入门到精通 - 实践

Jetpack Room:从入门到精通

  • 概述
  • 一、为什么选择 Room?
    • 1.对比原生 SQLite
    • 2.对比greenDao
  • 二、核心组件
    • 1. @Entity (实体类)
    • 2. @Dao (数据访问对象)
    • 3. @Database (数据库)
  • 三、基本操作
    • 1. 添加依赖
    • 2. 创建实体 (User.kt)
    • 3. 创建 DAO (UserDao.kt)
    • 4. 创建数据库 (AppDatabase.kt)
    • 5. 在 Activity/Fragment 中使用
  • 四、进阶:精通之路
    • 1. 复杂查询 (@Query)
    • 2. 返回自定义对象
    • 3. 数据库关系
      • 3.1 一对多
      • 3.2 多对多
    • 4. 异步与响应式编程
    • 5. 数据库迁移
    • 6. 数据库创建/打开回调
    • 7. 类型转换器 (@TypeConverter)
  • 五、使用建议与注意事项
    • 1.使用建议
    • 2.注意事项

概述

Room 是 Google 推出的 Android 官方持久化库,它在 SQLite 的基础上提供了一个抽象层,极大地简化了数据库操作。它通过编译时的 SQL 验证和注解,让开发者能够更安全、更高效地使用 SQLite。

一、为什么选择 Room?

1.对比原生 SQLite

  • 减少样板代码:无需手动编写 SQLiteOpenHelper、ContentValues、Cursor 解析等繁琐代码。
  • 编译时 SQL 验证:在编译阶段检查 SQL 语句的正确性,避免运行时崩溃。
  • 与 LiveData/Flow 集成:查询结果可以直接返回 LiveData 或 Flow,实现数据变化自动通知 UI。
  • 支持 Kotlin 协程:DAO 方法可以声明为 suspend 函数,完美集成协程。
  • 迁移支持:提供便捷的数据库版本迁移机制。
  • 官方推荐:Jetpack 组件,与 Android 生态深度集成。

2.对比greenDao

对比维度RoomGreenDao
开发公司Google官方(Jetpack架构组件)GreenRobot(第三方开源库)
支持平台Android,深度集成LiveData/ViewModelAndroid,轻量级ORM,兼容性广
数据库类型SQLite抽象层,类型安全,编译时SQL验证基于SQLite,代码生成策略,性能优化
API设计注解驱动(@Entity/@Dao),支持RxJava/Flow代码生成模式,自动生成DAO类,API简洁
性能表现插入1441ms/查询411ms(华为Mate10测试)插入2771ms/查询750ms(同条件测试),批量操作更快
缓存机制LiveData/Flowable自动缓存,实时UI更新内存高效映射,支持异步操作
数据类型支持强类型安全,支持自定义类型转换器基本类型支持,需手动处理复杂类型
事务支持编译时事务验证,集成Jetpack架构基础事务支持,需手动管理
社区与文档官方文档完善,更新频繁,生态成熟社区活跃,但更新较慢,文档分散
代码生成运行时通过注解处理器生成DAO实现编译时生成DAO类,减少样板代码
加密支持需自定义实现或第三方库原生支持数据库加密
学习曲线需掌握Jetpack架构,注解配置较复杂简单易用,快速上手,配置灵活
典型场景MVVM架构项目,需要实时数据同步高性能需求场景,批量数据操作

说明:

二、核心组件

Room 有三个主要组件:

1. @Entity (实体类)

代表数据库中的一张表。
使用 @Entity 注解标记一个数据类。
类中的每个属性(字段)默认对应表中的一列。

@Entity(tableName = "users") // 指定表名
data class User(
@PrimaryKey val uid: Int, // 主键
@ColumnInfo(name = "first_name") val firstName: String?, // 指定列名
@ColumnInfo(name = "last_name") val lastName: String?
)

2. @Dao (数据访问对象)

包含用于访问数据库的方法(增删改查)。
使用 @Dao 注解标记一个接口或抽象类。
DAO 是 Room 的核心,所有数据库操作都通过 DAO 完成。

@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAll(): List
@Query("SELECT * FROM users WHERE uid IN (:userIds)")
fun loadAllByIds(userIds: IntArray): List
@Query("SELECT * FROM users WHERE first_name LIKE :first AND " +
"last_name LIKE :last LIMIT 1")
fun findByName(first: String, last: String): User
@Insert
fun insertAll(vararg users: User)
@Update
fun update(user: User)
@Delete
fun delete(user: User)
}

3. @Database (数据库)

作为持久化数据的底层连接的主要访问点。
必须是一个抽象类,并继承自 RoomDatabase。
在注解中指定 entities(实体类)和 version(数据库版本)。

@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao // 获取 DAO 实例
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database" // 数据库文件名
).build()
INSTANCE = instance
instance
}
}
}
}

三、基本操作

1. 添加依赖

在 app/build.gradle 文件中添加:

dependencies {
def room_version = "2.6.1" // 请使用最新稳定版本
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// 如果使用 Kotlin 协程,还需要
implementation "androidx.room:room-ktx:$room_version"
// 如果使用 Kotlin,使用 kapt
kapt "androidx.room:room-compiler:$room_version"
}

2. 创建实体 (User.kt)

深色版本
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true) // 自增主键
val id: Long,
val name: String,
val email: String
)

3. 创建 DAO (UserDao.kt)

@Dao
interface UserDao {
@Query("SELECT * FROM users")
suspend fun getAllUsers(): List // 使用 suspend 支持协程
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Long): User?
@Insert
suspend fun insertUser(user: User): Long // 返回新插入行的主键
@Update
suspend fun updateUser(user: User)
@Delete
suspend fun deleteUser(user: User)
}

4. 创建数据库 (AppDatabase.kt)

@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"user_database"
)
// .addCallback(sRoomDatabaseCallback) // 可选:数据库创建/打开回调
.build()
INSTANCE = instance
instance
}
}
}
}

5. 在 Activity/Fragment 中使用

深色版本
class MainActivity : AppCompatActivity() {
private lateinit var userDao: UserDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val db = AppDatabase.getInstance(this)
userDao = db.userDao()
// 在协程中执行数据库操作
lifecycleScope.launch {
// 插入
val userId = userDao.insertUser(User(0, "Alice", "alice@example.com"))
Log.d("DB", "Inserted user with id: $userId")
// 查询
val users = userDao.getAllUsers()
Log.d("DB", "All users: $users")
}
}
}

四、进阶:精通之路

1. 复杂查询 (@Query)

@Query("SELECT * FROM users WHERE name LIKE :nameQuery ORDER BY name LIMIT :limit OFFSET :offset")
suspend fun searchUsers(nameQuery: String, limit: Int, offset: Int): List
@Query("SELECT COUNT(*) FROM users")
suspend fun getUserCount(): Int

2. 返回自定义对象

查询结果可以映射到非实体类的数据类。

data class UserNameAndEmail(
val name: String,
val email: String
)
@Dao
interface UserDao {
@Query("SELECT name, email FROM users")
suspend fun loadUserNamesAndEmails(): List
}

3. 数据库关系

Room 支持一对一、一对多、多对多关系,但需要手动处理。

3.1 一对多

例如 User 有多个 Pet。

深色版本
@Entity
data class Pet(
@PrimaryKey val petId: Long,
val name: String,
val userId: Long // 外键,关联 User.id
)
data class UserWithPets(
@Embedded val user: User,
@Relation(
parentColumn = "id",
entityColumn = "userId"
)
val pets: List
)
@Dao
interface UserDao {
@Transaction
@Query("SELECT * FROM User")
suspend fun getUsersWithPets(): List
}

3.2 多对多

需要一个中间表(Junction Table)。

@Entity(primaryKeys = ["userId", "bookId"])
data class UserBookCrossRef(
val userId: Long,
val bookId: Long
)
@Entity
data class Book(
@PrimaryKey val bookId: Long,
val title: String
)
data class UserWithBooks(
@Embedded val user: User,
@Relation(
entity = Book::class,
parentColumn = "id",
entityColumn = "bookId",
associateBy = Junction(UserBookCrossRef::class)
)
val books: List
)

4. 异步与响应式编程

返回 LiveData:数据变化时自动通知观察者。

@Query("SELECT * FROM users ORDER BY name")
fun loadUsers(): LiveData> // 不再是 suspend

返回 Flow:更强大的响应式流,支持协程。

@Query("SELECT * FROM users ORDER BY name")
fun getUsersFlow(): Flow>

在协程作用域中收集:

lifecycleScope.launch {
userDao.getUsersFlow().collect { users ->
// 更新 UI
}
}

5. 数据库迁移

当数据库结构变化(如添加列、修改表)时,需要升级版本并提供迁移策略。

val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE users ADD COLUMN last_updated INTEGER NOT NULL DEFAULT 0")
}
}
// 在构建数据库时添加
Room.databaseBuilder(context, AppDatabase::class.java, "database")
.addMigrations(MIGRATION_1_2)
.build()

6. 数据库创建/打开回调

private val sRoomDatabaseCallback = object : RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
// 数据库打开时执行
}
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
// 数据库创建时执行,可预填充数据
}
}

7. 类型转换器 (@TypeConverter)

将复杂对象(如 Date, List, 自定义对象)存储为数据库支持的类型(如 Long, String)。

深色版本
class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time
}
}
// 在数据库类中注册
@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
// ...
}

五、使用建议与注意事项

1.使用建议

Room 通过注解和编译时代码生成,极大地简化了 Android 上的 SQLite 操作。从定义实体、DAO 到构建数据库,整个过程清晰、类型安全。结合协程、LiveData 和 Flow,可以构建出响应迅速、用户体验良好的应用。

精通 Room 的关键在于:

2.注意事项

  • 不要在主线程执行数据库操作:Room 会抛出 IllegalStateException。使用 suspend 函数配合协程,或返回 LiveData/Flow。

  • 使用单例模式:数据库实例应全局唯一,避免频繁创建和销毁。

  • 合理设计实体和关系:避免过度复杂的关系查询。

  • 谨慎处理迁移:测试迁移脚本,避免数据丢失。

  • 利用编译时检查:Room 会在编译时报错,及时修复 SQL 语法错误。

  • 考虑数据量:对于超大数据集,考虑分页加载。

  • 使用 @Transaction:确保多个数据库操作的原子性。

http://www.jsqmd.com/news/2759/

相关文章:

  • ClickHouse index_granularity 详解 - 若
  • PADS笔记
  • 【2025最新教程】Claude Code国内使用_保姆级新手安装使用教程_最强AI编程工具
  • 如何计算sequence粒度的负载均衡损失 - 教程
  • P13885 [蓝桥杯 2023 省 Java/Python A] 反异或 01 串
  • clickhouse轻量级更新 - 若
  • 西电PCB设计指南第3章学习笔记
  • Vitrualbox、kali、metaspolitable2下载安装
  • LazyLLM端到端实战:用RAG+Agent实现自动出题与学习计划的个性化学习助手智能体
  • 补充图
  • 域名+邮件推送+事件总线=实现每天定时邮件!
  • llm入门环境
  • FLASH空间划分/存储数据至指定CODEFLASH位置
  • 深入解析:【C语言代码】数组排序
  • SOOMAL 降噪数据表
  • 案例分享|借助IronPDF IronOCR,打造医疗等行业的智能化解决方案
  • ClickHouse UPDATE 操作问题解决方案 - 若
  • 利用 Milvus + RustFS,快速打造一个 RAG!
  • Docker 私有镜像仓库 Harbor 安装部署带签名认证
  • ARC180 做题记
  • 借助Aspose.HTML控件,使用 Python 编辑 HTML
  • 微前端 micro-app 在vue 中的路由跳转问题
  • 1. 设计模式--工厂办法模式
  • 汽车视频总线采集过程中,如何兼顾响应速度和可靠性?
  • P8865 [NOIP2022] 种花
  • traefik 反向代理 + IdentityServer4
  • 麦角硫因制备关键技术和设备
  • 2025年十大好用网盘推荐:功能、口碑与性价比大对比
  • Word-通过宏格式化文档中的表格和图片
  • 反向代理 traefik - 健康检查