实体定义基础:@Entity、@PrimaryKey、表名与列名自定义、忽略字段、索引与唯一约束
原理概述
Room 使用 实体(Entity) 将 Kotlin/Java 数据类直接映射为 SQLite 数据库表。每个 @Entity 注解的类对应一张表,类中的属性(字段)默认映射为表的列。通过注解可以精细控制表名、列名、主键、索引、约束以及忽略某些不需要持久化的字段。该机制完全在编译期解析,生成 DAO 与数据库操作的实现代码,无需手写 SQL 建表语句。
核心注解一览
| 注解 | 作用 | 关键参数说明 |
|---|---|---|
@Entity | 标记类为数据库实体 | tableName:自定义表名;indices:定义索引数组;foreignKeys:外键约束;fullTextSearch:FTS 全文搜索 |
@PrimaryKey | 标记主键字段 | autoGenerate:是否自增(仅支持 Int/Long) |
@ColumnInfo | 自定义列名称、类型、排序规则等 | name:列名;typeAffinity:类型亲和性;defaultValue:默认值 |
@Ignore | 忽略某字段,不持久化到数据库 | 无参数,直接标注在字段或构造方法参数上 |
@Index | 定义单列或复合索引(在 @Entity.indices 中使用) | value:列名数组;unique:是否唯一约束 |
实体定义流程
- 创建一个数据类(Data Class 或 POJO),使用
@Entity注解标注。 - 指定主键:在唯一标识字段上添加
@PrimaryKey,可根据需要设置autoGenerate = true。 - 自定义表名:在
@Entity(tableName = "custom_table")中设置。 - 自定义列名:在每个字段上使用
@ColumnInfo(name = "col_name"),若不设置则默认使用字段名。 - 忽略字段:在不需要持久化的字段或构造参数上添加
@Ignore。 - 添加索引:在
@Entity的indices参数中定义@Index,可指定唯一约束。
完整实体示例
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(
tableName = "users",
indices = [
Index(value = ["email"], unique = true),
Index(value = ["first_name", "last_name"])
]
)
data class User(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "user_id")
val id: Long = 0,
@ColumnInfo(name = "first_name")
val firstName: String,
@ColumnInfo(name = "last_name")
val lastName: String,
@ColumnInfo(name = "email")
val email: String,
@ColumnInfo(name = "age", defaultValue = "0")
val age: Int = 0,
// 忽略字段:不存入数据库
@Ignore
val tempToken: String? = null
)
编译期 SQL 生成对照
上述实体对应的 CREATE TABLE 语句由 Room 自动生成(可在生成的 YourDatabase_Impl.java 中查看):
CREATE TABLE IF NOT EXISTS `users` (
`user_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`first_name` TEXT NOT NULL,
`last_name` TEXT NOT NULL,
`email` TEXT NOT NULL,
`age` INTEGER NOT NULL DEFAULT 0
);
CREATE UNIQUE INDEX IF NOT EXISTS `index_users_email` ON `users` (`email`);
CREATE INDEX IF NOT EXISTS `index_users_first_name_last_name` ON `users` (`first_name`, `last_name`);
实体与数据库表的映射关系(SVG)
常见错误与调试
❌ 未指定主键
每个实体必须有且仅有一个 @PrimaryKey 字段。Room 编译时会报错:Entities cannot have more than one primary key, or no primary key.
❌ 表名或列名与 SQLite 关键字冲突
Room 会自动使用反引号包裹列名,但自定义表名若使用保留字(如 order、group)可能导致 DAO 查询异常。建议始终使用下划线或前缀区分。
❌ 忽略字段标记在构造参数上
如果实体有多个构造方法,Room 只能识别一个。若想在某个构造参数上标记 @Ignore,必须确保该构造方法被标记为 @Ignore 或 Room 能自动匹配。
性能与稳定性优化建议
- 合理使用索引: 对经常出现在
WHERE、ORDER BY或JOIN条件中的列建立索引,但不要为所有列建索引(写性能下降)。 - 唯一约束: 若业务要求某字段值唯一(如邮箱、用户名),使用
@Index(unique = true)替代手动查重,数据库层保证原子性。 - 默认值优先于空值: 在
@ColumnInfo(defaultValue = "...")中设置默认值,减少插入时字段缺失导致的错误,并让查询结果更稳定。 - 忽略非持久化字段: 对于计算属性、临时 、缓存对象等,务必添加
@Ignore,避免 Room 尝试创建不兼容的列类型导致编译失败或运行时崩溃。 - 检查生成的 SQL: 在 Build 输出目录中找到
YourDatabase_Impl.java,查看createAllTables()方法中的 CREATE TABLE 语句,验证索引、约束、默认值是否正确。
部署注意事项
迁移与版本升级: 若在后续版本中修改了实体定义(如新增字段、修改索引、改变主键),必须编写 Migration 类并更新数据库版本,否则 Room 会抛出 IllegalStateException 强制卸载应用或清空数据。
开发阶段可使用 fallbackToDestructiveMigration() 临时跳过迁移,但生产环境绝不可用。