Room概述:SQLite的抽象层
Room是Google推出的SQLite数据库抽象层,作为Android Architecture Components的核心组件之一,它通过注解处理器在编译时生成代码,既保留了SQLite的灵活性,又提供了类型安全和便捷的API。相比直接使用SQLiteDatabase,Room大大减少了样板代码,同时支持协程、RxJava和LiveData等现代异步编程模式。
Room三大核心组件
- Entity(实体):定义数据库表结构,使用@Entity注解标记数据类
- DAO(数据访问对象):定义数据库操作方法,使用@Dao注解标记接口
- Database(数据库):数据库持有者,使用@Database注解标记抽象类
添加依赖
// build.gradle (Module: app)
dependencies {
// Room核心库
implementation "androidx.room:room-runtime:2.6.1"
kapt "androidx.room:room-compiler:2.6.1"
// Kotlin协程支持
implementation "androidx.room:room-ktx:2.6.1"
// 可选:RxJava支持
implementation "androidx.room:room-rxjava3:2.6.1"
// 可选:测试支持
testImplementation "androidx.room:room-testing:2.6.1"
}
// 确保应用kotlin-kapt插件
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
Entity:定义数据实体
Entity代表数据库中的表,每个Entity类对应一张表。通过注解可以配置表名、主键、索引、外键等属性。
/**
* 用户实体类
* 对应数据库中的users表
*/
@Entity(
tableName = "users",
indices = [
Index(value = ["email"], unique = true),
Index(value = ["department_id"])
]
)
data class User(
@PrimaryKey
@ColumnInfo(name = "id")
val id: String,
@ColumnInfo(name = "name")
val name: String,
@ColumnInfo(name = "email")
val email: String,
@ColumnInfo(name = "age", defaultValue = "0")
val age: Int = 0,
@ColumnInfo(name = "avatar_url")
val avatarUrl: String? = null,
@ColumnInfo(name = "department_id")
val departmentId: String? = null,
@ColumnInfo(name = "created_at")
val createdAt: Long = System.currentTimeMillis(),
@ColumnInfo(name = "is_active")
val isActive: Boolean = true
)
/**
* 部门实体类
* 演示外键关系
*/
@Entity(
tableName = "departments",
foreignKeys = [
ForeignKey(
entity = Company::class,
parentColumns = ["id"],
childColumns = ["company_id"],
onDelete = ForeignKey.CASCADE
)
]
)
data class Department(
@PrimaryKey
val id: String,
@ColumnInfo(name = "name")
val name: String,
@ColumnInfo(name = "company_id")
val companyId: String
)
/**
* 使用复合主键
*/
@Entity(
tableName = "user_tasks",
primaryKeys = ["user_id", "task_id"]
)
data class UserTask(
@ColumnInfo(name = "user_id")
val userId: String,
@ColumnInfo(name = "task_id")
val taskId: String,
@ColumnInfo(name = "assigned_at")
val assignedAt: Long = System.currentTimeMillis()
)
Entity注解详解
- @Entity:标记类为数据库实体,可指定tableName
- @PrimaryKey:标记主键,可设置autoGenerate自动生成
- @ColumnInfo:配置列属性,如name、defaultValue、typeAffinity
- @Ignore:标记字段不存入数据库
- @Embedded:嵌入其他对象,字段平铺到当前表
- @Relation:定义表关系,用于一对多查询
DAO:数据访问对象
DAO定义了访问数据库的方法,Room会在编译时生成实现类。支持插入、更新、删除、查询等基本操作,以及事务控制和原始SQL查询。
@Dao
interface UserDao {
// ==================== 插入操作 ====================
@Insert
suspend fun insert(user: User)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrUpdate(user: User)
@Insert
suspend fun insertAll(users: List<User>): List<Long>
@Insert
suspend fun insertAndGetId(user: User): Long
// ==================== 更新操作 ====================
@Update
suspend fun update(user: User): Int
@Update
suspend fun updateAll(users: List<User>): Int
// ==================== 删除操作 ====================
@Delete
suspend fun delete(user: User): Int
@Delete
suspend fun deleteAll(users: List<User>): Int
@Query("DELETE FROM users WHERE id = :userId")
suspend fun deleteById(userId: String): Int
@Query("DELETE FROM users")
suspend fun deleteAllUsers()
// ==================== 查询操作 ====================
@Query("SELECT * FROM users")
suspend fun getAll(): List<User>
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getById(userId: String): User?
@Query("SELECT * FROM users WHERE email = :email LIMIT 1")
suspend fun getByEmail(email: String): User?
@Query("SELECT * FROM users WHERE age >= :minAge AND age <= :maxAge")
suspend fun getByAgeRange(minAge: Int, maxAge: Int): List<User>
@Query("SELECT * FROM users WHERE name LIKE '%' || :keyword || '%'")
suspend fun searchByName(keyword: String): List<User>
@Query("SELECT * FROM users ORDER BY created_at DESC LIMIT :limit")
suspend fun getRecentUsers(limit: Int): List<User>
// ==================== Flow支持 ====================
@Query("SELECT * FROM users")
fun getAllAsFlow(): Flow<List<User>>
@Query("SELECT * FROM users WHERE department_id = :deptId")
fun getByDepartmentAsFlow(deptId: String): Flow<List<User>>
@Query("SELECT COUNT(*) FROM users")
fun getUserCountAsFlow(): Flow<Int>
// ==================== 分页查询 ====================
@Query("SELECT * FROM users ORDER BY name ASC LIMIT :limit OFFSET :offset")
suspend fun getUsersPaged(limit: Int, offset: Int): List<User>
// ==================== 聚合查询 ====================
@Query("SELECT COUNT(*) FROM users")
suspend fun getUserCount(): Int
@Query("SELECT AVG(age) FROM users")
suspend fun getAverageAge(): Float?
@Query("SELECT department_id, COUNT(*) as count FROM users GROUP BY department_id")
suspend fun getUserCountByDepartment(): List<DepartmentCount>
data class DepartmentCount(
@ColumnInfo(name = "department_id")
val departmentId: String,
@ColumnInfo(name = "count")
val count: Int
)
// ==================== 关联查询 ====================
@Transaction
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserWithDepartment(userId: String): UserWithDepartment?
data class UserWithDepartment(
@Embedded
val user: User,
@Relation(
parentColumn = "department_id",
entityColumn = "id"
)
val department: Department?
)
// ==================== 事务操作 ====================
@Transaction
suspend fun updateUserAndDepartment(user: User, department: Department) {
// 在同一个事务中执行多个操作
insert(user)
// 自动回滚如果任一操作失败
}
}
冲突策略
- ABORT:默认值,回滚事务
- FAIL:仅失败当前SQL语句
- IGNORE:忽略冲突,继续执行
- REPLACE:替换现有数据
- ROLLBACK:回滚当前事务
Database:数据库配置
Database类是Room数据库的入口,负责管理数据库版本、实体注册、DAO提供和迁移策略。
@Database(
entities = [
User::class,
Department::class,
UserTask::class
],
version = 3, // 数据库版本号
exportSchema = true // 导出schema用于版本控制
)
@TypeConverters(DateConverter::class, ListConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun departmentDao(): DepartmentDao
abstract fun userTaskDao(): UserTaskDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
}
}
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database.db"
)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
// 数据库首次创建时的回调
// 可以预填充数据
}
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
// 数据库打开时的回调
}
})
.fallbackToDestructiveMigration() // 降级时重建数据库(开发环境使用)
.build()
}
// 版本1到2的迁移
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"ALTER TABLE users ADD COLUMN avatar_url TEXT"
)
}
}
// 版本2到3的迁移
val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
// 创建新表
database.execSQL(
"""
CREATE TABLE users_new (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
email TEXT NOT NULL,
age INTEGER NOT NULL DEFAULT 0,
avatar_url TEXT,
is_active INTEGER NOT NULL DEFAULT 1
)
""".trimIndent()
)
// 复制数据
database.execSQL(
"""
INSERT INTO users_new (id, name, email, age, avatar_url)
SELECT id, name, email, age, avatar_url FROM users
""".trimIndent()
)
// 删除旧表
database.execSQL("DROP TABLE users")
// 重命名新表
database.execSQL("ALTER TABLE users_new RENAME TO users")
}
}
}
}
/**
* 类型转换器
* 用于存储复杂类型
*/
class DateConverter {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time
}
}
class ListConverter {
private val gson = Gson()
@TypeConverter
fun fromString(value: String?): List<String>? {
return value?.let {
val listType = object : TypeToken<List<String>>() {}.type
gson.fromJson(it, listType)
}
}
@TypeConverter
fun fromList(list: List<String>?): String? {
return list?.let { gson.toJson(it) }
}
}
Repository模式与协程集成
在实际项目中,通常将Room操作封装在Repository层,结合协程和Flow实现响应式数据访问。
class UserRepository @Inject constructor(
private val userDao: UserDao,
private val userApi: UserApi,
private val connectivityObserver: ConnectivityObserver
) {
/**
* 获取所有用户,自动从网络同步
*/
fun getAllUsers(): Flow<Result<List<User>>> = flow {
emit(Result.Loading)
// 先加载本地数据
val localUsers = userDao.getAll()
if (localUsers.isNotEmpty()) {
emit(Result.Success(localUsers))
}
// 有网络时同步远程数据
if (connectivityObserver.isOnline) {
try {
val remoteUsers = userApi.getUsers()
userDao.insertAll(remoteUsers)
emit(Result.Success(remoteUsers))
} catch (e: Exception) {
if (localUsers.isEmpty()) {
emit(Result.Error(e))
}
}
}
}.flowOn(Dispatchers.IO)
/**
* 实时观察用户列表变化
*/
fun observeUsers(): Flow<List<User>> {
return userDao.getAllAsFlow()
.flowOn(Dispatchers.IO)
}
/**
* 搜索用户
*/
fun searchUsers(keyword: String): Flow<List<User>> = flow {
if (keyword.isBlank()) {
emit(emptyList())
return@flow
}
val users = userDao.searchByName(keyword)
emit(users)
}.flowOn(Dispatchers.IO)
/**
* 添加用户
*/
suspend fun addUser(user: User): Result<Unit> = withContext(Dispatchers.IO) {
try {
// 先插入本地
userDao.insert(user)
// 同步到服务器
if (connectivityObserver.isOnline) {
userApi.createUser(user)
}
Result.Success(Unit)
} catch (e: Exception) {
Result.Error(e)
}
}
/**
* 更新用户
*/
suspend fun updateUser(user: User): Result<Unit> = withContext(Dispatchers.IO) {
try {
userDao.update(user)
if (connectivityObserver.isOnline) {
userApi.updateUser(user)
}
Result.Success(Unit)
} catch (e: Exception) {
Result.Error(e)
}
}
/**
* 删除用户
*/
suspend fun deleteUser(userId: String): Result<Unit> = withContext(Dispatchers.IO) {
try {
userDao.deleteById(userId)
if (connectivityObserver.isOnline) {
userApi.deleteUser(userId)
}
Result.Success(Unit)
} catch (e: Exception) {
Result.Error(e)
}
}
/**
* 分页加载
*/
suspend fun getUsersPage(page: Int, pageSize: Int): Result<List<User>> {
return withContext(Dispatchers.IO) {
try {
val offset = (page - 1) * pageSize
val users = userDao.getUsersPaged(pageSize, offset)
Result.Success(users)
} catch (e: Exception) {
Result.Error(e)
}
}
}
}
// ViewModel中使用
@HiltViewModel
class UserListViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {
private val _searchQuery = MutableStateFlow("")
val uiState: StateFlow<UserListUiState> = combine(
repository.observeUsers(),
_searchQuery
) { users, query ->
val filtered = if (query.isBlank()) {
users
} else {
users.filter { it.name.contains(query, ignoreCase = true) }
}
UserListUiState(users = filtered)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = UserListUiState()
)
fun onSearchQueryChange(query: String) {
_searchQuery.value = query
}
fun deleteUser(user: User) {
viewModelScope.launch {
repository.deleteUser(user.id)
}
}
data class UserListUiState(
val users: List<User> = emptyList(),
val isLoading: Boolean = false
)
}
高级特性与最佳实践
掌握Room的高级特性,如 FTS全文搜索、预填充数据库、数据库加密等,可以应对更复杂的业务场景。
// ==================== FTS全文搜索 ====================
@Entity
@Fts4(languageId = "lid") // 启用FTS4
data class ArticleFts(
@PrimaryKey
@ColumnInfo(name = "rowid")
val rowId: Int,
@ColumnInfo(name = "title")
val title: String,
@ColumnInfo(name = "content")
val content: String
)
@Dao
interface ArticleFtsDao {
@Query("SELECT * FROM article_fts WHERE article_fts MATCH :query")
suspend fun search(query: String): List<ArticleFts>
}
// ==================== 预填充数据库 ====================
@Database(entities = [User::class], version = 1)
abstract class PrepopulatedDb : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
fun create(context: Context): PrepopulatedDb {
return Room.databaseBuilder(
context,
PrepopulatedDb::class.java,
"app.db"
)
.createFromAsset("database/app.db") // 从assets加载
// .createFromFile(File("/path/to/app.db"))
.build()
}
}
}
// ==================== 数据库加密(SQLCipher)====================
@Database(entities = [User::class], version = 1)
abstract class EncryptedDb : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
fun create(context: Context, passphrase: String): EncryptedDb {
val factory = SupportFactory(SQLiteDatabase.getBytes(passphrase.toCharArray()))
return Room.databaseBuilder(
context,
EncryptedDb::class.java,
"encrypted.db"
)
.openHelperFactory(factory)
.build()
}
}
}
// ==================== 多实例数据库 ====================
@Database(entities = [User::class], version = 1)
abstract class MultiUserDb : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private val instances = ConcurrentHashMap<String, MultiUserDb>()
fun getInstance(context: Context, userId: String): MultiUserDb {
return instances.getOrPut(userId) {
Room.databaseBuilder(
context,
MultiUserDb::class.java,
"user_$userId.db"
).build()
}
}
fun closeInstance(userId: String) {
instances.remove(userId)?.close()
}
}
}
Room使用注意事项
- Entity类必须有一个主键
- Entity类不能包含循环引用
- 查询返回的POJO必须有默认构造函数
- 数据库操作必须在后台线程执行
- 注意处理数据库迁移,避免数据丢失
- 大数据量查询考虑使用分页
性能优化建议
- 使用@Transaction注解包裹多个相关操作
- 对频繁查询的列添加索引
- 使用Flow观察数据变化,避免轮询
- 批量操作使用insertAll/updateAll
- 复杂查询使用@RawQuery支持动态SQL
- 启用WAL模式提升并发性能