Jetpack Room 从入门到精通

Jetpack Room:从入门到精通

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

概述

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

对比维度 Room GreenDao
开发公司 Google官方(Jetpack架构组件) GreenRobot(第三方开源库)
支持平台 Android,深度集成LiveData/ViewModel Android,轻量级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 注解标记一个数据类。

类中的每个属性(字段)默认对应表中的一列。

Kotlin 复制代码
@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 完成。

Kotlin 复制代码
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAll(): List<User>

    @Query("SELECT * FROM users WHERE uid IN (:userIds)")
    fun loadAllByIds(userIds: IntArray): List<User>

    @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(数据库版本)。

Kotlin 复制代码
@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 文件中添加:

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)

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

3. 创建 DAO (UserDao.kt)

Kotlin 复制代码
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    suspend fun getAllUsers(): List<User> // 使用 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)

Kotlin 复制代码
@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 中使用

Kotlin 复制代码
深色版本
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。
Kotlin 复制代码
@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<User>

@Query("SELECT COUNT(*) FROM users")
suspend fun getUserCount(): Int

2. 返回自定义对象

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

Kotlin 复制代码
data class UserNameAndEmail(
    val name: String,
    val email: String
)

@Dao
interface UserDao {
    @Query("SELECT name, email FROM users")
    suspend fun loadUserNamesAndEmails(): List<UserNameAndEmail>
}

3. 数据库关系

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

3.1 一对多

例如 User 有多个 Pet。

Kotlin 复制代码
深色版本
@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<Pet>
)

@Dao
interface UserDao {
    @Transaction
    @Query("SELECT * FROM User")
    suspend fun getUsersWithPets(): List<UserWithPets>
}

3.2 多对多

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

Kotlin 复制代码
@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<Book>
)

4. 异步与响应式编程

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

Kotlin 复制代码
@Query("SELECT * FROM users ORDER BY name")
fun loadUsers(): LiveData<List<User>> // 不再是 suspend

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

Kotlin 复制代码
@Query("SELECT * FROM users ORDER BY name")
fun getUsersFlow(): Flow<List<User>>

在协程作用域中收集:

Kotlin 复制代码
lifecycleScope.launch {
    userDao.getUsersFlow().collect { users ->
        // 更新 UI
    }
}

5. 数据库迁移

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

Kotlin 复制代码
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. 数据库创建/打开回调

Kotlin 复制代码
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)。

Kotlin 复制代码
深色版本
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:确保多个数据库操作的原子性。

相关推荐
云上凯歌12 分钟前
02 Spring Boot企业级配置详解
android·spring boot·后端
cly115 分钟前
Ansible自动化(十三):调试与优化
数据库·自动化·ansible
QQ_43766431418 分钟前
redis相关命令讲解及原理
数据库·redis·缓存
hqiangtai21 分钟前
Android 高级专家技术能力图谱
android·职场和发展
萤丰信息31 分钟前
开启园区“生命体”时代——智慧园区系统,定义未来的办公与生活
java·大数据·运维·数据库·人工智能·生活·智慧园区
aqi0033 分钟前
FFmpeg开发笔记(九十七)国产的开源视频剪辑工具AndroidVideoEditor
android·ffmpeg·音视频·直播·流媒体
stevenzqzq41 分钟前
Android Koin 注入入门教程
android·kotlin
TDengine (老段)1 小时前
TDengine Rust 连接器进阶指南
大数据·数据库·物联网·rust·时序数据库·tdengine·涛思数据
二哈喇子!1 小时前
MySQL数据库操作命令【SQL语言】
数据库·sql·视图与索引
China_Yanhy1 小时前
AWS S3 深度配置指南:每一栏每个选项有什么作用
java·数据库·aws