name: Room Database description: Android Room持久化库的专家技能 version: 1.0.0 category: Android数据存储 slug: android-room status: active
Room数据库技能
概述
本技能提供Android Room持久化库的专家级能力。它支持设计数据库架构、实现DAO、配置迁移,并与现代Android架构组件集成。
允许使用的工具
bash- 执行Gradle命令和Android构建工具read- 分析Room实体和DAO文件write- 生成Room数据库组件edit- 更新现有的Room配置glob- 搜索数据库相关文件grep- 在数据库代码中搜索模式
能力
实体设计
-
实体定义
- 使用适当的注解定义@Entity类
- 配置主键(单一和复合)
- 设置外键关系
- 为查询优化配置索引
- 实现嵌入式对象
-
类型转换器
- 为自定义类型创建@TypeConverter
- 处理日期/时间转换
- 将枚举转换为数据库类型
- 将复杂对象序列化为JSON
- 配置全局类型转换器
DAO实现
-
查询方法
- 使用SQL编写@Query注解
- 实现@Insert、@Update、@Delete
- 配置冲突策略
- 创建复杂的JOIN查询
- 实现分页查询
-
响应式查询
- 返回Flow以支持响应式更新
- 配置LiveData返回类型
- 实现一次性挂起函数
- 处理可空结果
- 创建参数化查询
数据库配置
-
数据库设置
- 配置@Database注解
- 设置数据库构建器
- 配置预填充数据库
- 实现多数据库
- 为测试配置内存数据库
-
迁移
- 实现Migration对象
- 配置自动迁移
- 处理破坏性迁移
- 使用MigrationTestHelper测试迁移
- 设计回退策略
集成
-
Hilt集成
- 使用@Singleton提供数据库
- 将DAO注入到仓库中
- 配置数据库作用域
- 处理多模块设置
-
仓库模式
- 实现仓库接口
- 处理离线优先逻辑
- 配置缓存策略
- 实现同步机制
目标流程
本技能与以下流程集成:
android-room-database.js- Room实现offline-first-architecture.js- 离线数据策略mobile-security-implementation.js- 安全数据存储
依赖
必需
- Android Studio
- Room 2.6+
- Kotlin 1.9+
- KSP或KAPT
可选
- Hilt用于依赖注入
- Kotlin协程
- Paging 3库
配置
Gradle设置
// build.gradle.kts (app)
plugins {
id("com.google.devtools.ksp")
}
dependencies {
implementation(libs.room.runtime)
implementation(libs.room.ktx)
ksp(libs.room.compiler)
// 可选 - Paging 3集成
implementation(libs.room.paging)
// 测试
testImplementation(libs.room.testing)
}
版本目录
# gradle/libs.versions.toml
[versions]
room = "2.6.1"
[libraries]
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
room-paging = { group = "androidx.room", name = "room-paging", version.ref = "room" }
room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" }
使用示例
实体定义
// data/local/entity/UserEntity.kt
package com.example.app.data.local.entity
import androidx.room.*
@Entity(
tableName = "users",
indices = [
Index(value = ["email"], unique = true),
Index(value = ["created_at"])
]
)
data class UserEntity(
@PrimaryKey
@ColumnInfo(name = "id")
val id: String,
@ColumnInfo(name = "email")
val email: String,
@ColumnInfo(name = "display_name")
val displayName: String,
@ColumnInfo(name = "avatar_url")
val avatarUrl: String?,
@ColumnInfo(name = "created_at")
val createdAt: Long,
@ColumnInfo(name = "updated_at")
val updatedAt: Long,
@Embedded(prefix = "settings_")
val settings: UserSettings
)
data class UserSettings(
@ColumnInfo(name = "notifications_enabled")
val notificationsEnabled: Boolean = true,
@ColumnInfo(name = "theme")
val theme: String = "system"
)
带关系的实体
// data/local/entity/PostEntity.kt
package com.example.app.data.local.entity
import androidx.room.*
@Entity(
tableName = "posts",
foreignKeys = [
ForeignKey(
entity = UserEntity::class,
parentColumns = ["id"],
childColumns = ["author_id"],
onDelete = ForeignKey.CASCADE
)
],
indices = [Index(value = ["author_id"])]
)
data class PostEntity(
@PrimaryKey
@ColumnInfo(name = "id")
val id: String,
@ColumnInfo(name = "author_id")
val authorId: String,
@ColumnInfo(name = "title")
val title: String,
@ColumnInfo(name = "content")
val content: String,
@ColumnInfo(name = "published_at")
val publishedAt: Long?,
@ColumnInfo(name = "is_draft")
val isDraft: Boolean = true
)
// 用于查询的关系类
data class PostWithAuthor(
@Embedded val post: PostEntity,
@Relation(
parentColumn = "author_id",
entityColumn = "id"
)
val author: UserEntity
)
类型转换器
// data/local/converter/Converters.kt
package com.example.app.data.local.converter
import androidx.room.TypeConverter
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): LocalDateTime? {
return value?.let {
LocalDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneId.systemDefault())
}
}
@TypeConverter
fun toTimestamp(date: LocalDateTime?): Long? {
return date?.atZone(ZoneId.systemDefault())?.toInstant()?.toEpochMilli()
}
@TypeConverter
fun fromStringList(value: List<String>?): String? {
return value?.joinToString(",")
}
@TypeConverter
fun toStringList(value: String?): List<String>? {
return value?.split(",")?.map { it.trim() }
}
}
DAO实现
// data/local/dao/UserDao.kt
package com.example.app.data.local.dao
import androidx.room.*
import kotlinx.coroutines.flow.Flow
@Dao
interface UserDao {
@Query("SELECT * FROM users ORDER BY display_name ASC")
fun observeAllUsers(): Flow<List<UserEntity>>
@Query("SELECT * FROM users WHERE id = :userId")
fun observeUserById(userId: String): Flow<UserEntity?>
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: String): UserEntity?
@Query("SELECT * FROM users WHERE email = :email LIMIT 1")
suspend fun getUserByEmail(email: String): UserEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: UserEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUsers(users: List<UserEntity>)
@Update
suspend fun updateUser(user: UserEntity)
@Delete
suspend fun deleteUser(user: UserEntity)
@Query("DELETE FROM users WHERE id = :userId")
suspend fun deleteUserById(userId: String)
@Query("DELETE FROM users")
suspend fun deleteAllUsers()
@Transaction
suspend fun replaceAllUsers(users: List<UserEntity>) {
deleteAllUsers()
insertUsers(users)
}
}
带关系的DAO
// data/local/dao/PostDao.kt
package com.example.app.data.local.dao
import androidx.room.*
import androidx.paging.PagingSource
import kotlinx.coroutines.flow.Flow
@Dao
interface PostDao {
@Transaction
@Query("SELECT * FROM posts WHERE is_draft = 0 ORDER BY published_at DESC")
fun observePublishedPostsWithAuthor(): Flow<List<PostWithAuthor>>
@Transaction
@Query("SELECT * FROM posts WHERE is_draft = 0 ORDER BY published_at DESC")
fun getPublishedPostsPagingSource(): PagingSource<Int, PostWithAuthor>
@Transaction
@Query("SELECT * FROM posts WHERE id = :postId")
suspend fun getPostWithAuthor(postId: String): PostWithAuthor?
@Query("SELECT * FROM posts WHERE author_id = :authorId ORDER BY published_at DESC")
fun observePostsByAuthor(authorId: String): Flow<List<PostEntity>>
@Query("""
SELECT * FROM posts
WHERE title LIKE '%' || :query || '%'
OR content LIKE '%' || :query || '%'
ORDER BY published_at DESC
""")
suspend fun searchPosts(query: String): List<PostEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertPost(post: PostEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertPosts(posts: List<PostEntity>)
@Update
suspend fun updatePost(post: PostEntity)
@Query("UPDATE posts SET is_draft = :isDraft WHERE id = :postId")
suspend fun updateDraftStatus(postId: String, isDraft: Boolean)
@Delete
suspend fun deletePost(post: PostEntity)
}
数据库定义
// data/local/AppDatabase.kt
package com.example.app.data.local
import androidx.room.*
import com.example.app.data.local.converter.Converters
import com.example.app.data.local.dao.PostDao
import com.example.app.data.local.dao.UserDao
import com.example.app.data.local.entity.PostEntity
import com.example.app.data.local.entity.UserEntity
@Database(
entities = [
UserEntity::class,
PostEntity::class
],
version = 2,
autoMigrations = [
AutoMigration(from = 1, to = 2)
],
exportSchema = true
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun postDao(): PostDao
}
Hilt模块
// di/DatabaseModule.kt
package com.example.app.di
import android.content.Context
import androidx.room.Room
import com.example.app.data.local.AppDatabase
import com.example.app.data.local.dao.PostDao
import com.example.app.data.local.dao.UserDao
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides
@Singleton
fun provideAppDatabase(
@ApplicationContext context: Context
): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"app_database"
)
.fallbackToDestructiveMigration()
.build()
}
@Provides
fun provideUserDao(database: AppDatabase): UserDao = database.userDao()
@Provides
fun providePostDao(database: AppDatabase): PostDao = database.postDao()
}
仓库实现
// data/repository/UserRepositoryImpl.kt
package com.example.app.data.repository
import com.example.app.data.local.dao.UserDao
import com.example.app.data.local.entity.UserEntity
import com.example.app.data.remote.api.UserApi
import com.example.app.domain.model.User
import com.example.app.domain.repository.UserRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
class UserRepositoryImpl @Inject constructor(
private val userDao: UserDao,
private val userApi: UserApi
) : UserRepository {
override fun observeUsers(): Flow<List<User>> {
return userDao.observeAllUsers().map { entities ->
entities.map { it.toDomain() }
}
}
override fun observeUser(userId: String): Flow<User?> {
return userDao.observeUserById(userId).map { it?.toDomain() }
}
override suspend fun refreshUsers() {
val remoteUsers = userApi.getUsers()
val entities = remoteUsers.map { it.toEntity() }
userDao.replaceAllUsers(entities)
}
override suspend fun getUser(userId: String): User? {
return userDao.getUserById(userId)?.toDomain()
}
}
// 用于映射的扩展函数
private fun UserEntity.toDomain() = User(
id = id,
email = email,
displayName = displayName,
avatarUrl = avatarUrl
)
质量门限
数据完整性
- 外键约束正确配置
- 频繁查询的列上建立索引
- 适当位置设置唯一约束
- 正确的级联删除行为
性能
- 使用EXPLAIN优化查询
- 大数据集的分页处理
- 后台线程执行
- 正确的索引策略
测试
- 使用内存数据库进行DAO测试
- 迁移测试
- 仓库集成测试
相关技能
kotlin-compose- Android UI开发offline-storage- 跨平台模式mobile-security- 加密数据库
版本历史
- 1.0.0 - 支持Room 2.6的初始版本