第1章:Room核心概念与关系映射总览
Android开发者使用SQLite存储关系型数据时,很容易陷入模板代码与手动管理数据库版本的泥潭。Room是Google官方推出的持久化库,它在SQLite之上提供了一层抽象,通过编译时注解处理器自动生成实现,将数据库操作与Java/Kotlin对象自然绑定。
本章作为全课程的开篇,用一张全局结构图帮你建立Room组件与关系映射的完整认知,并逐一介绍Entity、DAO、Database、关系类型及编译时验证的核心角色。
💡 核心要点:Room并非全自动ORM。它要求开发者显式定义实体、DAO和数据库对象,但通过编译时注解处理提供类型安全、SQL验证和关系映射的自动化代码生成。映射关系(1:1、1:N、M:N)需配合
@Relation与嵌套POJO实现。
1.1 实体(Entity)——表结构定义的强制契约
每个 @Entity 注解的类对应一个SQLite表。Room约定:每个实体必须有一个主键(@PrimaryKey),且构造函数参数必须映射所有列(除非使用@Ignore)。Kotlin用户推荐使用data class以减少模板代码。
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
@ColumnInfo(name = "user_name")
val userName: String,
val age: Int? = null
)
字段默认使用Kotlin属性名作为列名,可通过@ColumnInfo重命名。@Ignore用于忽略不在表中的字段。@Embedded可扁平化嵌套对象。
1.2 数据访问对象(DAO)——类型安全的SQL入口
DAO必须是接口或抽象类,使用@Dao注解。Room在编译时生成实现。每个操作必须用@Insert、@Update、@Delete或@Query标记。
@Dao
interface UserDao {
@Insert
suspend fun insert(user: User): Long
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUserById(id: Long): User?
@Query("SELECT * FROM users WHERE user_name LIKE :query")
suspend fun searchUsers(query: String): List<User>
@Delete
suspend fun delete(user: User)
}
⚠️ 编译时验证生效点:Room会在编译期校验
@Query中的SQL语法、参数占位符:param与函数参数是否匹配、返回值类型能否映射到实体。如果SQL写错表名或列名,编译直接报错。
1.3 数据库(Database)——中央持有者与版本管控
@Database注解类必须继承RoomDatabase,并定义获取所有DAO的抽象方法。数据库版本号用于迁移管理。
@Database(
entities = [User::class, UserProfile::class, Playlist::class, Song::class],
version = 1,
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun playlistDao(): PlaylistDao
}
| 属性 | 说明 | 示例 |
|---|---|---|
entities | 列出所有实体类 | [User::class, Song::class] |
version | 数据库版本号,升级后需提供Migration | 1 → 2 |
exportSchema | 导出Schema到JSON文件用于测试 | true |
1.4 关系映射类型(一对一、一对多、多对多)
Room不直接使用外键约束,而是通过嵌套POJO + @Relation注解来构建关系查询结果。列表对比三种关系的典型场景:
| 关系类型 | 实体示例 | @Relation使用方式 | 典型查询 |
|---|---|---|---|
| 一对一 (1:1) | User ↔ UserProfile | @Relation(parentColumn = "id", entityColumn = "userId") | 每个用户有其唯一档案 |
| 一对多 (1:N) | User ↔ Playlist | @Relation(parentColumn = "id", entityColumn = "ownerId") + List<Playlist> | 一个用户拥有多个播放列表 |
| 多对多 (M:N) | Playlist ↔ Song | 使用关联表(PlaylistSongRef) + @Relation两个方向 | 播放列表与歌曲多对多交叉 |
📌 关键区别:一对一/一对多只需在POJO中声明
@Relation属性,多对多必须在@Relation中设置associateBy参数指定交叉表实体。
1.5 编译时验证机制——防错于未然
Room的注解处理器在编译时完成以下检查:
- 实体合法性:每个实体必须有一个且仅有一个
@PrimaryKey;不能有多个自增主键。 - SQL语法:
@Query内的SQL语句会被解析验证,不通过则编译失败。 - 参数与返回值:占位符
:name与函数参数名称匹配;返回值类型必须能从查询结果构建(列名匹配字段或通过@ColumnInfo映射)。 - 关系注解一致性:
parentColumn和entityColumn必须存在,且类型兼容。 - 索引与唯一约束:通过
@Entity(indices = [])可在编译时声明索引,避免运行时full table scan。
得益于这些验证,很多数据库错误能在IDE敲代码阶段就被发现,大幅减少运行期崩溃。
⚠️ 常见陷阱:
• 实体中声明了
• DAO中
• 关系查询忘加
• 实体中声明了
@PrimaryKey(autoGenerate = true)但Kotlin属性用Int而非Long —— Room会报警告,自增主键推荐使用Long。• DAO中
@Query使用SELECT *但返回类型是单个对象的List —— 类型不匹配编译错误。• 关系查询忘加
@Transaction注解 —— 运行时报错:Cannot access database on the main thread(非直接原因,但关系查询一般需事务)。
本章建立了Room的全局图景。接下来的章节会深入每个关系映射类型的完整实现,手把手带你编写实体、DAO、关系POJO、@Transaction查询以及数据库构建代码。