实体定义基础:@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:是否唯一约束

实体定义流程

  1. 创建一个数据类(Data Class 或 POJO),使用 @Entity 注解标注。
  2. 指定主键:在唯一标识字段上添加 @PrimaryKey,可根据需要设置 autoGenerate = true。
  3. 自定义表名:在 @Entity(tableName = "custom_table") 中设置。
  4. 自定义列名:在每个字段上使用 @ColumnInfo(name = "col_name"),若不设置则默认使用字段名。
  5. 忽略字段:在不需要持久化的字段或构造参数上添加 @Ignore。
  6. 添加索引:在 @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)

实体类 → 数据库表映射关系 User 实体类 @Entity(tableName = "users") @PrimaryKey(autoGenerate=true) @ColumnInfo(name="user_id") val id: Long @ColumnInfo(name="first_name") val firstName: String @ColumnInfo(name="last_name") val lastName: String @Ignore val tempToken: String? users 表 user_id (INTEGER PK) first_name (TEXT NOT NULL) last_name (TEXT NOT NULL) email (TEXT NOT NULL) age (INTEGER DEFAULT 0) tempToken 无对应列 (被 @Ignore 忽略) 索引: UNIQUE (email) → index_users_email (first_name, last_name) → index_users_fn_ln 映射总结 1. @Entity → table 2. @PrimaryKey → PK 3. @ColumnInfo → column name 4. @Ignore → 无列 5. @Index → 索引 / 唯一 6. autoGenerate → AUTOINCREMENT 7. defaultValue → DEFAULT

常见错误与调试

❌ 未指定主键
每个实体必须有且仅有一个 @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() 临时跳过迁移,但生产环境绝不可用。