3. Entity 基础:创建第一个实体类、@Entity 注解、@PrimaryKey、字段命名、表名指定
3.1 本章核心概念
在 Room 中,Entity(实体类) 是数据持久化的最小单元。每个 Entity 对应数据库中的一张表,实体类的每个字段(非静态、非 transient)默认对应表的一列。本章将带领您从零构建第一个 Entity,理解 @Entity、@PrimaryKey 注解的正确用法,并掌握表名、字段命名的最佳实践。
| 概念 | 说明 |
|---|---|
| @Entity | 标记一个类为 Room 实体,默认表名为类名(不区分大小写)。 |
| @PrimaryKey | 标记一个字段为主键,支持自增(autoGenerate)。 |
| 字段命名 | 默认使用字段名映射列名,可通过 @ColumnInfo 自定义。 |
| 表名指定 | 在 @Entity(tableName = "xxx") 中显式指定。 |
💡 设计建议:表名使用下划线命名法(如
user_profile),字段名使用驼峰或下划线均可,但建议保持全小写下划线风格以保证数据库兼容性。
下图为 Entity → 数据库表的核心映射关系:
3.2 创建第一个实体类
以用户信息表为例,演示完整的 Entity 定义步骤。
3.2.1 添加依赖
确保 build.gradle(模块级)中已包含 Room 依赖:
// build.gradle (模块级)
dependencies {
def room_version = "2.6.1"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version" // Java 使用
// 如果使用 Kotlin: kapt "androidx.room:room-compiler:$room_version"
}
3.2.2 编写实体类
// User.java
package com.example.app.data.entity;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "user")
public class User {
@PrimaryKey(autoGenerate = true)
private int id;
private String name;
private int age;
// 构造方法(Room 需要无参构造或全参构造)
public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}
// Getter / Setter(必须,Room 通过反射读写字段)
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
⚠️ 重要规则:Room 实体类必须暴露每个持久化字段的 Getter 和 Setter(或使用 Kotlin 属性)。如果字段为
private 但没有 Getter/Setter,Room 会编译时报错“Cannot find getter for field”。
3.3 @Entity 注解详解
@Entity 注解位于类声明之上,可选的配置参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
tableName | String | 类名(小写) | 指定数据库表名,推荐使用下划线命名 |
indices | Index[] | 空数组 | 定义索引,提高查询性能 |
primaryKeys | String[] | 由 @PrimaryKey 决定 | 定义复合主键(多个字段组合) |
foreignKeys | ForeignKey[] | 空数组 | 定义外键约束 |
ignoredColumns | String[] | 空数组 | 忽略某些字段,不映射到数据库列 |
💡 建议:始终显式指定
tableName,避免因类名重构导致表名意外变化。例如:@Entity(tableName = "user_info")
3.4 @PrimaryKey 的使用
@PrimaryKey 用于标记实体中的主键字段。常用两种模式:
- 手动赋值:不设置 autoGenerate,由程序员在插入前手动设置 id 值。
- 自动生成:设置
autoGenerate = true,Room 在插入时自动分配整数自增值。
@PrimaryKey(autoGenerate = true)
private int id; // 推荐:数据库自动递增
⚠️ 注意:
autoGenerate 仅对整数类型(int / Long)有效。对于 String 类型主键,必须手动赋值且不能 autoGenerate。
3.5 字段命名与列名映射
默认情况下,字段名就是列名。如果需要定制列名,使用 @ColumnInfo:
import androidx.room.ColumnInfo;
public class User {
@PrimaryKey(autoGenerate = true)
private int id;
@ColumnInfo(name = "full_name")
private String name;
@ColumnInfo(name = "user_age")
private int age;
}
字段命名常见风格对比:
| 风格 | Java 示例 | 数据库列名 | 推荐度 |
|---|---|---|---|
| 驼峰 | private String userName; | userName | 中等(SQL 中不区分大小写,易混淆) |
| 下划线 | private String user_name; | user_name | 推荐(数据库习惯,可读性高) |
| @ColumnInfo 映射 | @ColumnInfo(name = "user_name") | user_name | 最佳(代码保持驼峰,表用下划线) |
3.6 实现流程归纳
- 定义包结构:通常将 Entity 类放在
data/entity包下。 - 编写实体类:添加
@Entity注解,设置tableName。 - 声明主键:添加
@PrimaryKey并决定是否 autoGenerate。 - 定义字段和 Getter/Setter:所有持久化字段必须提供访问方法。
- 编译验证:执行
Build → Make Project,若注解处理成功则无报错。
3.7 常见错误与调试
| 错误信息 | 原因 | 解决方法 |
|---|---|---|
Cannot find getter for field 'xxx' | 缺少 Getter 方法 | 添加对应 getXxx() 方法 |
Entities and Pojos must have a usable public constructor | 缺少无参构造 | 添加 public User() {} 或使用全参构造 |
Error: @Entity class must have a @PrimaryKey | 未定义主键 | 至少为⼀个字段添加 @PrimaryKey |
Multiple primary keys defined | 定义了多个 @PrimaryKey(非复合) | 要么只用一个,要么使用 @Entity(primaryKeys = {}) 指定复合主键 |
3.8 性能与稳定性建议
- 主键类型:推荐使用
int或long配合autoGenerate = true,性能优于 String 类型。 - 避免冗余字段:Entity 中只声明数据库需要的字段,无关字段用
@Ignore注解排除(将在后续章节讲解)。 - 表名冲突:不同 Entity 的 tableName 不能重复,否则编译时报错
Multiple tables with the same name。
📌 关键总结:一个 Entity 类 +
@Entity(tableName = "...") + @PrimaryKey + 完整 Getter/Setter = 一张可操作的数据库表。命名规范决定代码可维护性,建议统一使用下划线表名和驼峰字段名加 @ColumnInfo 映射。