定义 Entity 实体类:@Entity、主键、列名、索引
Room 的 Entity 就是你数据库里那张表的“映射”。说白了,你写一个 Kotlin 数据类,加上 @Entity 注解,Room 就知道:“哦,这玩意儿要变成一张表。”
我刚学 Room 那会儿,觉得 Entity 不就是个普通类吗?加个注解就完事了。结果项目跑起来,一堆诡异错误……嗯,后来才明白,注解玩不转,数据库里全是雷。今天咱们就把 @Entity、主键、列名、索引这几个核心概念掰开揉碎。
1. @Entity:声明表的起点
@Entity 标注在类上,告诉 Room 这个类对应一张表。你可以设置 tableName 来指定表名,默认用类名(下划线风格)。
@PrimaryKey 字段,否则编译就报错。
@Entity(tableName = "users")
data class User(
@PrimaryKey val id: Int,
val name: String?,
val age: Int
)
这个例子很简单,但有坑。比如 id 我用 Int 类型,如果数据库里自增主键是 Long,插入时就会炸。我曾在某个老项目里吃过这个亏——字段类型一定要和 SQLite 的实际类型匹配,Room 虽然做了隐式转换,但坑还是不少。
2. 主键:@PrimaryKey
主键是每张表的灵魂。Room 里的 @PrimaryKey 可以加在单个字段上,也可以加在多个字段上形成复合主键。
- 自动生成主键:
@PrimaryKey(autoGenerate = true),适合整型字段。 - 复合主键:在
@Entity注解中用primaryKeys参数指定多个字段名。
我个人习惯:只要业务主键不是自然键(比如用户ID可以传过来),我都用 autoGenerate 的自增主键。省心。
// 单字段自增主键
@PrimaryKey(autoGenerate = true) val id: Long = 0
// 复合主键
@Entity(primaryKeys = ["firstName", "lastName"])
data class NameEntity(
val firstName: String,
val lastName: String,
val age: Int
)
3. 列名:@ColumnInfo
默认情况下,Room 会把 Kotlin 字段名直接当做列名(驼峰转下划线?实际上 Room 并不会自动转换!)。我第一次用 Room 时,字段是 userName,数据库里也存成 userName,后来同事说“应该用下划线命名规范”。我就加了 @ColumnInfo(name = "user_name")。
@ColumnInfo 还可以设置 typeAffinity(列类型亲和性)、defaultValue(默认值)、index(是否创建索引)等参数。不过我个人用得最多的是 name 和 defaultValue。
@Entity(tableName = "users")
data class User(
@PrimaryKey val id: Int,
@ColumnInfo(name = "user_name", defaultValue = "unknown") val name: String
)
这里有个小细节:defaultValue 必须是 SQL 合法的默认值表达式,比如 "unknown" 或者 "0",Room 不会帮你加引号。我自己曾写 defaultValue = "unknown" 没加引号,结果建表时 SQL 报错,因为 unknown 被当成了列名……
4. 索引:@Index
索引是提升查询性能的秘密武器,但也会拖慢写入速度。Room 通过 @Entity 的 indices 参数来创建索引。
| 参数 | 说明 | 示例 |
|---|---|---|
value |
索引包含的列名(数组) | ["name", "age"] |
unique |
是否唯一索引 | true |
name |
自定义索引名(生成的 SQL 会用到) | "idx_user_name" |
@Entity(
tableName = "users",
indices = [
Index(value = ["name"], name = "idx_user_name"),
Index(value = ["name", "age"], unique = true)
]
)
data class User(
@PrimaryKey val id: Int,
@ColumnInfo(name = "user_name") val name: String,
val age: Int
)
你想想看,如果你的 App 经常按 name 和 age 组合查询,给这两个字段建一个复合索引就很划算。但别滥用——每多一个索引,插入数据就多一次写索引的操作。我曾在线上环境看到一位同事给三个字段各建一个索引,数据量一上来,插入性能直接腰斩。
@ColumnInfo(index = true) 也可以给单个字段快速加索引,但它不能设置唯一约束。如果你想唯一 + 索引,还是用 @Entity(indices) 配合 unique = true。
知识脉络一览
| 注解/参数 | 用途 | 常见坑点 |
|---|---|---|
@Entity(tableName) |
声明表,自定义表名 | 表名不用特殊符号,Room 不支持自动转换驼峰 |
@PrimaryKey |
设置主键(单字段/复合) | 复合主键字段不可为 null,autoGenerate 需用 Long |
@ColumnInfo(name, defaultValue) |
自定义列名和默认值 | defaultValue 字符串要自己加引号 |
@Index(unique, name) |
创建索引(可唯一) | 索引过多影响写入性能 |
一句话总结
写 Entity 时先想清楚:表名、主键策略、列名是否要改、需要哪些索引。这四个点确定好了,你的数据库模型就稳了一半。剩下的另一半,靠的是运行时行为和迁移策略——那将是后面章节的内容。