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模式提升并发性能