Room 是 Android Jetpack 中的 ORM 库,它在 SQLite 之上提供抽象层,用于在 Android 上处理本地数据库。根据官方文档 Android Developers, 2025,Room 在编译时基于注解自动生成 DAO 实现,与直接使用 SQLiteOpenHelper 相比,消除了大约 70% 的样板代码。该库在编译阶段执行 SQL 查询验证,从而在应用程序在设备上运行之前发现语法错误。
要点
Room 是 Android Jetpack 中的 ORM 库,由 Google 创建,用于简化 Android 平台上本地 SQLite 数据库的操作。它提供注解来描述数据模式,并在编译阶段自动生成 DAO 接口的实现。与直接使用 SQLiteOpenHelper 不同,Room 使开发人员免于编写大量样板代码来创建、打开和管理数据库连接。
该库在 Google I/O 2017 上作为 Android 架构组件的一部分推出。从那时起,Room 已成为本地数据存储的事实标准,在流行度上超过了 Android 的 GreenDAO 和 Realm 等解决方案。据 Google 数据,该库用于 Google Play 中发布的 60% 以上的处理设备本地数据的应用程序。
关键特性 — 使用注解处理器在编译阶段验证 SQL 查询。如果开发人员在 SQL 命令中出错,例如指定了不存在的列名,编译将在安装应用程序之前以错误结束。这与 SQLiteOpenHelper 方法完全不同,在这种方法中,此类错误仅在运行时才发现,通常是在生产环境中。
SQLite 只支持五种数据类型:TEXT、INTEGER、REAL、BLOB 和 NULL。但是在 Java 和 Kotlin 中使用了复杂类型:Date、List、Enum 和自定义对象。为了存储它们,Room 提供了 TypeConverters 机制 — 将复杂类型转换为 SQLite 可理解的原始类型的静态方法。例如,Date 对象转换为 Long(时间戳),List<String> 通过 Gson 或 Moshi 转换为 JSON 字符串。
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
val db = Room
.databaseBuilder(context, AppDatabase::class.java, "app-db")
.build()
要声明转换器,只需将 @TypeConverter 注解添加到静态方法,并在数据库级别的 @TypeConverters 注解中指定转换器类。Room 在每个 SQL 查询中读取和写入相应类型时自动应用转换器,无需手动调用转换方法。
Room 由三个主要组件组成:Entity、DAO 和 Database。每个组件都扮演着严格定义的角色,并用相应的注解进行标注。它们一起形成了一个完整的数据访问层,将应用程序的业务逻辑与 SQLite 的实现细节隔离开来。
Entity 是一个数据类,描述数据库中一个表的结构。类的每个字段对应表的一列,数据库中的每一行对应类的一个实例。@Entity 注解告诉 Room 该类是一个表。带有 @PrimaryKey 注解的字段定义主键,可以是自增的或复合的。对于表之间的关系,使用 @ForeignKey,确保数据库级别的数据完整性。
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "full_name")
val name: String,
val age: Int,
val email: String
)
DAO(数据访问对象)是一个接口或抽象类,声明用于处理数据的操作:插入、读取、更新和删除。每个操作都用 @Insert、@Query、@Update 或 @Delete 进行注解。Room 在编译阶段自动生成此接口的实现。@Query 注解具有特殊价值,它接收 SQL 查询作为字符串并在构建阶段检查其正确性。
@Dao
interface UserDao {
@Insert
suspend fun insert(user: User): Long
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User?
@Query("SELECT * FROM users")
fun getAllUsers(): Flow<List<User>>
@Delete
suspend fun delete(user: User)
}
Database 是一个抽象类,继承自 RoomDatabase,作为数据库的入口点。它包含所有 Entity 的列表,并提供获取 DAO 的抽象方法。该类用 @Database 注解,其中指定了架构版本和实体列表。数据库实例的创建通过 Room.databaseBuilder 完成,指定应用程序上下文、文件名和 Database 类。
Room 不取代 SQLite,而是作为抽象层在其之上工作。内部架构包括注解处理器、代码生成器和连接池。在编译阶段,注解处理器分析 Entity、DAO 和 Database 类,然后生成带有 _Impl 后缀的实现类。所有生成的类都放置在构建包中,开发人员无法直接看到。
编译阶段的代码生成 — Room 的核心机制。对于每个 DAO 接口,都会生成一个包含所有注解方法完整实现的类。来自 @Query 注解的 SQL 查询会检查正确性:处理器将列名与 Entity 字段匹配,并检查 SQL 语法。检测到错误时,编译会中断并显示清晰的消息。这在使用原始 SQLiteOpenHelper 时是不可能的,在那种情况下错误只在运行时出现。
生成过程包括三个阶段。第一 — 模式验证:处理器检查 @Database 中列出的所有类是否为有效的 Entity。第二 — DAO 主体生成:为每个方法创建实现,使用内部 RoomSQLiteQuery 对象执行预准备查询。第三 — Database_Impl 类的生成,实现数据库的创建和打开以及所有 DAO 对象的初始化。
class UserDao_Impl(private val __db: RoomDatabase) : UserDao {
private val __insertionAdapter = __db
.createInsertionAdapter(User::class, 0)
override suspend fun insert(user: User): Long {
__db.assertNotSuspendingTransaction()
return __db.runInTransaction {
__insertionAdapter.insertAndReturnId(user)
}
}
}
Room 不为数据库操作创建单独的线程池。默认情况下,查询在调用线程中执行,但有一个限制:读写会阻塞线程。对于异步工作,Room 通过 suspend 函数与 Kotlin 协程集成,通过返回值与 LiveData 集成,通过响应式包装器与 Flow 集成。这为开发人员针对特定任务选择架构解决方案提供了灵活性。
让我们看一个使用 Room 创建笔记存储应用程序的实际示例。该应用程序包含一个带有 id、title、content 和 timestamp 字段的 Note 表。用户可以添加、查看和删除笔记。演示使用了协程进行异步操作。
要将 Room 连接到 Android 项目,需要在应用程序模块的 build.gradle 文件中添加依赖项。Room 需要三个组件:运行时库、kapt 注解处理器和可选的协程支持。库版本在 room_version 变量中指定,便于更新。从 Room 2.4.0 开始,KSP 作为 kapt 的替代方案被支持,具有更高的构建速度。
dependencies {
def room_version = "2.6.1"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
// 可选:测试
testImplementation "androidx.room:room-testing:$room_version"
}
配置依赖项后,创建三个文件:Note Entity、NoteDao 接口和 AppDatabase 类。Note Entity 包含带有 @PrimaryKey 和 @ColumnInfo 注解的字段。DAO 提供插入、获取列表和删除的方法。Database 通过 @Database 注解连接 Entity 和 DAO。
@Entity(tableName = "notes")
data class Note(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val title: String,
val content: String,
@ColumnInfo(name = "created_at")
val timestamp: Long = System.currentTimeMillis()
)
@Dao
interface NoteDao {
@Insert
suspend fun insert(note: Note)
@Query("SELECT * FROM notes ORDER BY created_at DESC")
fun getAllNotes(): Flow<List<Note>>
@Delete
suspend fun delete(note: Note)
}
AppDatabase 文件声明为继承 RoomDatabase 的抽象类。在 @Database 注解中,列出了当前版本的所有 Entity 和架构版本号。使用单例模式通过 Room.databaseBuilder 的 build 方法获取实例,并传入应用程序上下文。缓存数据库实例可防止可能导致内存泄漏的多次创建。
迁移 是 Room 中的一种机制,用于在更新应用程序时更改数据库架构而不丢失现有数据。当用户安装带有修改后的 Entity 的新版本时,Room 检测到版本不匹配并执行指定的迁移步骤。如果没有迁移,数据库将被删除并重新创建,导致所有已保存的用户数据丢失。
迁移由 Migration 类描述,它接收数据库的初始和最终版本。在 migrate 方法内部,执行 ALTER TABLE 或 CREATE TABLE SQL 查询以更改架构。Room 无法自动检测架构更改 — 开发人员必须为每个 Entity 更改手动编写迁移。从 Room 2.4.0 版本开始,提供了实验性的 autoMigrations 功能用于自动生成迁移。
autoMigrations 功能允许 Room 基于 Entity 版本之间的差异自动生成迁移。要使用它,只需在 @Database 中添加 @AutoMigration 注解并指定架构导出到 JSON。Room 比较相邻版本的架构并生成必要的 ALTER 查询。然而 autoMigrations 只支持向后兼容的更改:添加列、创建索引和具有兼容转换的类型更改。
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"ALTER TABLE users ADD COLUMN phone TEXT"
)
}
}
val db = Room
.databaseBuilder(context, AppDatabase::class.java, "app-db")
.addMigrations(MIGRATION_1_2)
.build()
添加复杂更改时,例如重命名列或合并表,需要使用中间表进行手动迁移。典型场景:创建一个具有旧架构的临时表,将数据从旧表复制到新表并进行转换,删除旧表并重命名临时表。Room 保证所有迁移都在一个事务中执行,并且在发生错误时更改将完全回滚。
常见问题
Room 提供带有注解和编译阶段 SQL 验证的 ORM 抽象,而 SQLiteOpenHelper 需要手动编写所有查询和管理连接。Room 自动生成 CRUD 操作的代码,并与 Android 架构组件(包括 LiveData 和 Flow)集成。
Room 支持所有 Java 原始类型:Int、Long、Boolean、Float、Double,以及 String、ByteArray 和 Date。对于复杂类型,如 List 或 Enum,使用 TypeConverters — 将非标准类型转换为 SQLite 支持格式的静态转换方法。
是的,Room 支持没有协程的同步调用,但它们会阻塞执行的线程。对于异步工作,可以使用 LiveData 或 RxJava 代替协程。Google 建议在新项目中使用协程作为异步数据访问的主要方式。
如果 Room 检测到数据库版本不匹配并且找不到合适的迁移,默认会抛出 IllegalStateException,并附带错误描述。开发人员可以使用 fallbackToDestructiveMigration 方法覆盖此行为,该方法将删除现有数据库并创建新数据库,导致所有数据丢失。
Room 通过带有 @Embedded 注解的嵌套对象和带有 @Relation 注解的关系类支持关系。对于涉及表连接的复杂查询,使用自定义 POJO 类,其字段从带有 SQL JOIN 运算符的 @Query 结果中填充。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。