Jetpack Room 从入门到精通 - 实践

概述

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架构项目,需要实时数据同步高性能需求场景,批量数据操作

说明:

  • 表格对齐:使用 : 控制对齐方式(默认居左,:–: 居中,–: 居右)。
  • 标题加粗:通过 ** 标记对比维度标题,提升可读性。
  • 兼容性:可直接复制到支持 Markdown 的平台(如 GitHub、Typora、Notion 等)使用。

二、核心组件

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)

  • 参数绑定:使用 :paramName 绑定方法参数。
  • 集合参数:使用 IN (:ids) 查询集合。
  • 模糊查询:LIKE ‘%’ || :name || ‘%’。
  • 排序与分页:ORDER BY, LIMIT, OFFSET。
  • 聚合函数:COUNT, SUM, AVG, MAX, MIN。
@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 的关键在于:

  • 熟练掌握 @Query 的各种用法。
  • 理解并正确处理数据库关系。
  • 灵活运用 LiveData 和 Flow 实现响应式 UI。
  • 掌握数据库迁移和类型转换器。
  • 遵循最佳实践,编写高效、可靠的数据库代码。

2.注意事项

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

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

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

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

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

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

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

posted @ 2025-09-24 11:52  yxysuanfa  阅读(22)  评论(0)    收藏  举报