Room:关键概念、Entity、DAO 及数据库操作

作者: IT Sectr 发布日期: 2026-05-04 阅读时间: 8 分钟

Room 是一个用于在 Android 中操作 SQLite 的库,属于 Jetpack 的一部分。它在原始 SQLite 之上提供了一层抽象,自动完成了表的创建、查询的执行以及数据到 Kotlin 和 Java 对象的转换。根据 Android Developers 的资料,Room 在构建阶段编译 SQL 查询,检查语法正确性以及 Entity 与表之间的关系。

要点

  • Room — 用于 Android 应用中 SQLite 操作的 Jetpack ORM 库。
  • Entity — 使用 @Entity 注解的类,每个实例对应表中的一行。
  • DAO — 包含注解方法用于 SQL 查询的数据访问对象。
  • Database — 继承 RoomDatabase 的抽象类,连接 Entity 和 DAO。
  • 迁移 — 安全更改数据库模式而不丢失用户数据的机制。

什么是 Room 以及为什么需要它

Room 是 Android Jetpack 中的一个持久化库,为 SQLite 提供对象关系映射。Room 解决了原始 SQLite 的三个主要问题:编写大量样板代码来创建表、缺少编译时 SQL 查询检查以及手动将 Cursor 转换为对象。

该库使用注解编译器(kapt 或 KSP),在构建阶段生成抽象类 RoomDatabase 和 DAO 的实现。这保证了 SQL 中的语法错误和类型不匹配在应用启动之前就能被发现,而不是在发布到 Google Play 之后的运行时。

根据 Google I/O 2023 的数据,Room 被 68% 的处理本地数据的 Android 应用所使用。它是设备上数据存储的标准,被 Google 推荐用于所有新项目——取代已过时的 SQLiteOpenHelper 和 ContentProvider。

在需要本地缓存服务器数据、离线模式或需要复杂 SQL 查询的结构化用户数据存储的项目中实施 Room。

Room 是 Android Jetpack 的一部分,被 Google 官方推荐用于所有处理本地数据的新项目。与 Realm 或 ObjectBox 不同,Room 使用原生 SQLite,这保证了与任何第三方数据库工具的兼容性——从 DB Browser 到 DataGrip。开发人员可以打开应用的 .db 文件并直接执行 SQL 查询,从而简化开发过程中的调试和数据分析。

Room 中的 Entity 和注解

Entity 是使用 @Entity 注解的数据类,Room 将其转换为数据库表。类的每个字段成为表的一列,每个实例成为一行。Room 使用反射来访问字段,因此需要使用 @PrimaryKey 注解来标识必需的标识符。

基本注解

@Entity 注解告知 Room 该类是一个表。tableName 参数指定表的名称(如果与类名不同)。@PrimaryKey 定义主键,可通过 autoGenerate = true 实现自动生成。

kotlin
@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    @ColumnInfo(name = "full_name")
    val name: String,
    @Ignore
    val tempData: String?
)

@ColumnInfo 指定表中列的名称(如果与 Kotlin 字段名不同)。@Ignore 将字段从表中排除——它不会被保存到数据库。@ForeignKey 描述表之间的外键关系,支持删除或更新时的级联操作。

Room 通过 @Embedded 注解支持嵌套对象。嵌套类的字段会展开为父表的列,并带有前缀以避免名称冲突。例如,包含 city 和 street 字段的 Address 类嵌入到 User 中时,会在 users 表中创建 address_city 和 address_street 列,从而无需为简单的值对象创建单独的表。

转换类型

Room 仅支持原始类型及其包装类。对于存储列表、Date 或自定义类型,需要使用 @TypeConverter——在自定义类型和 SQLite 原始类型之间进行转换的静态方法,例如 List 和 JSON 字符串之间的转换。

DAO 和 SQL 查询

DAO(数据访问对象)是使用 @Dao 注解的接口或抽象类,包含用于访问数据的方法。每个方法都使用 SQL 操作进行注解:@Insert、@Update、@Delete 或带有显式 SQL 查询的 @Query。

编译时检查的 @Query

@Query 注解接受一个 SQL 字符串,Room 在编译时检查其语法正确性以及列名与 Entity 字段的匹配。Room 通过 :paramName 语法支持参数化查询。

kotlin
@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :userId")
    suspend fun getUserById(userId: Int): User?

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUser(user: User)

    @Query("SELECT * FROM users ORDER BY name ASC")
    fun getAllUsers(): Flow<List<User>>
}

@Insert 支持 OnConflictStrategy 策略来处理插入重复记录时的冲突。Flow 作为返回类型确保表中每次数据变化时 UI 都能反应式更新——订阅会在每次 INSERT、UPDATE 或 DELETE 时自动重启。

用于复杂操作的 @Transaction

@Transaction 注解保证多个操作在单个事务块中原子化执行。Room 在执行期间锁定数据库,防止多线程并发访问时的竞态条件。

Database 和模式迁移

RoomDatabase 是一个抽象类,将 Entity 和 DAO 统一到一个数据库访问点。它通过 Room.databaseBuilder 创建,需要指定模式版本和 Entity 类列表。建议通过 lazy 委托将数据库实例创建为单例,以避免多个连接。

迁移

Room 中的迁移是一个 Migration 类,描述从旧模式版本到新版本的 SQL 脚本。如果在模式更改时未提供迁移,Room 会抛出 IllegalStateException。这可以防止在应用更新时意外丢失用户数据。

kotlin
val migration_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE users ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
    }
}

val db = Room.databaseBuilder(
    getApplication(),
    AppDatabase::class.java,
    "app_database"
).addMigrations(migration_1_2)
 .build()

在开发中可以使用 fallbackToDestructiveMigration,它在版本不匹配时删除旧数据库并创建新数据库。此模式仅用于调试——在生产版本中必须编写迁移脚本。

Room 提供了专门的 Room.inMemoryTestBuilder 类用于测试数据库,它在 RAM 中创建数据库而不保存到磁盘。每次测试完成后,数据库会自动销毁,保证测试场景的完全隔离。结合 android-arch-core-testing 库,开发人员可以管理数据库的生命周期并检查迁移的正确性,而无需手动清理状态。

Room 的性能直接取决于查询和索引的结构。Room 提供了 enableQueryCallback 标志用于分析慢查询,它会记录所有 SQL 查询及其执行时间。开发人员可以使用此日志找到执行时间超过 100 毫秒的查询,并通过在 @Entity 中使用 @Index 注解添加复合索引,或使用 @Relation 将子查询重写为直接 JOIN 连接来优化它们。

Room 还支持通过 SQLCipher 进行数据库加密。连接 net.zetetic:android-database-sqlcipher 库并使用 SupportFactory 替代标准工厂,可以在不更改 DAO 查询和 Entity 结构的情况下透明地加密磁盘上的所有数据。这对于处理用户个人数据的应用是必需的,符合 GDPR 和俄罗斯联邦 152-FZ 个人数据保护法的要求。加密密码可以存储在 Android Keystore 中,以防止在已 root 设备上通过工具提取。

Room 与 Kotlin Coroutines 和 Flow

Room 从 2.1 版本开始原生支持 Kotlin Coroutines。DAO 方法可以是挂起函数,在后台执行查询而不阻塞主线程。Room 自动管理调度器,使用 Dispatchers.IO 进行读写查询。

对于反应式查询,Room 返回 Flow——一种冷数据流,在受影响的表每次发生变化时发出新值。ViewModel 通过 stateIn 或 collect 订阅 Flow,确保 UI 自动更新,无需手动通知适配器。

Room 还通过专门的 PagingSource 实现支持 Paging 3,从 SQLite 分页加载数据。这对于包含数千条记录的大列表非常有效:Paging 3 只加载屏幕上可见的行,并在数据库发生变化时自动更新它们。

在显示新闻列表、操作日志或具有离线访问和无尽滚动功能的商品列表时,将 Paging 3 与 Room 结合使用。

常见问题

Room 与 SQLiteOpenHelper 有什么不同?

Room 自动完成表的创建、Cursor 到对象的转换以及编译时 SQL 检查。SQLiteOpenHelper 需要手动编写模式、处理 Cursor,并且在应用启动前不进行查询检查,这增加了出错的风险。

每次模式更改都需要编写迁移吗?

是的,在更改 Entity(添加/删除字段、更改类型)时需要迁移。没有迁移时 Room 会在启动时抛出 IllegalStateException。在开发中可以启用 fallbackToDestructiveMigration,但在发布版本中必须编写正确的迁移脚本。

Room 支持表之间的关系吗?

Room 支持用于级联操作的 @ForeignKey 和用于嵌套对象的 @Relation。对于复杂的 JOIN 查询,使用 @Transaction 注解和 @Query,通过 @Embedded 和 @Relation 返回包含嵌套实体的 POJO。

可以在不使用 Kotlin 的情况下用 Java 使用 Room 吗?

是的,Room 与 Java 完全兼容。使用 LiveData 或 RxJava Observable 替代挂起函数,使用 LiveData 替代 Flow。Room 与 Java 支持所有相同的注解,但需要更多的样板代码来处理异步操作。

Room 数据库加密如何工作?

Room 通过 Zetetic 的 SQLCipher 支持加密。使用 net.zetetic:android-database-sqlcipher 库中的 SupportFactory 替代 Room.databaseBuilder,并传入加密密码。磁盘上的所有数据将对 DAO 查询透明地加密。

总结

  • Room — 用于 SQLite 的 Jetpack ORM 库,支持编译时 SQL 检查。
  • @Entity 描述表,@PrimaryKey — 标识符,@ColumnInfo — 列名。
  • @Dao 包含 @Query、@Insert、@Update 和 @Delete 方法用于数据访问。
  • RoomDatabase 连接 Entity 和 DAO,通过 Room.databaseBuilder 创建。
  • 迁移(Migration)描述用于更改模式而不丢失数据的 SQL 脚本。
  • Room 原生支持 Kotlin Coroutines(挂起函数)和 Flow 用于反应式 UI 更新。
  • 对于大列表,使用 Room 通过 PagingSource 实现 Paging 3 的分页加载。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读