跳到主要内容

Room 数据库

问题

Room 的核心组件是什么?如何处理复杂查询和数据库迁移?

答案

三大核心组件

Entity 定义

@Entity(
tableName = "articles",
indices = [Index(value = ["author_id"])],
foreignKeys = [ForeignKey(
entity = User::class,
parentColumns = ["id"],
childColumns = ["author_id"],
onDelete = ForeignKey.CASCADE
)]
)
data class Article(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val content: String,
@ColumnInfo(name = "author_id") val authorId: Long,
@ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis()
)

DAO 操作

@Dao
interface ArticleDao {
// 返回 Flow:数据变化自动通知
@Query("SELECT * FROM articles ORDER BY created_at DESC")
fun observeAll(): Flow<List<Article>>

// 关联查询
@Transaction
@Query("SELECT * FROM articles WHERE id = :id")
fun getArticleWithAuthor(id: Long): Flow<ArticleWithAuthor>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(article: Article): Long

@Update
suspend fun update(article: Article)

@Query("DELETE FROM articles WHERE id = :id")
suspend fun deleteById(id: Long)
}

// 关联数据类
data class ArticleWithAuthor(
@Embedded val article: Article,
@Relation(parentColumn = "author_id", entityColumn = "id")
val author: User
)

Database 配置

@Database(
entities = [Article::class, User::class],
version = 2,
exportSchema = true
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun articleDao(): ArticleDao
abstract fun userDao(): UserDao
}

// Hilt 提供单例
@Module @InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides @Singleton
fun provideDatabase(@ApplicationContext context: Context): AppDatabase =
Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.addMigrations(MIGRATION_1_2)
.build()

@Provides
fun provideArticleDao(db: AppDatabase) = db.articleDao()
}

数据库迁移

val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE articles ADD COLUMN tags TEXT DEFAULT ''")
}
}

// 自动迁移(Room 2.4+,简单场景)
@Database(
version = 3,
autoMigrations = [
AutoMigration(from = 2, to = 3)
]
)
迁移测试

导出 schema(exportSchema = true)并编写迁移测试,确保不同版本用户升级不丢数据:

@RunWith(AndroidJUnit4::class)
class MigrationTest {
@get:Rule
val helper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java
)

@Test
fun migrate1To2() {
helper.createDatabase("test.db", 1).apply { close() }
helper.runMigrationsAndValidate("test.db", 2, true, MIGRATION_1_2)
}
}

常见面试问题

Q1: Room 是如何实现编译期 SQL 检查的?

答案

Room 使用注解处理器(KSP / kapt)在编译期解析 @Query 中的 SQL 语句,验证表名、列名、参数类型是否正确,并生成对应的 DAO 实现类。如果 SQL 语法错误或字段不匹配,编译期就会报错。

Q2: Room 的 Flow 返回是如何实现数据监听的?

答案

Room 内部使用 InvalidationTracker 监听数据库表的变化。当表数据发生 INSERT/UPDATE/DELETE 时,InvalidationTracker 会通知对应的 Flow,触发重新查询并发射新数据。这是基于 SQLite 的 updateHook 实现的。

Q3: @Transaction 注解的作用?

答案

@Transaction 保证查询在同一个事务中执行,主要用于:

  • 关联查询(@Relation):先查主表再查关联表,事务保证数据一致性
  • 批量操作:多条语句原子执行

相关链接