0%

hibernate注解方式

Hibernate 注解方式深度解析

Hibernate 注解方式是替代传统 XML 映射配置的主流方案,其核心优势在于配置与实体类代码耦合度低、开发效率高、可读性强。相较于 XML 配置需要维护独立的 .hbm.xml 文件,注解可直接嵌入实体类的属性或 getter 方法上,大幅简化了配置流程。本文将从核心注解分类、关联关系映射、高级配置(如复合主键、枚举映射)等维度,系统讲解 Hibernate 注解的使用方法,并补充关键注意事项。

核心基础注解:实体与主键配置

基础注解主要解决「实体类与数据库表的映射」和「主键生成策略」两大核心问题,是所有注解配置的基础。

实体类级注解

用于标识类为 Hibernate 实体,并指定对应的数据库表信息。

注解 作用说明 常用属性
@Entity 标识当前类是持久化实体,必须添加(否则 Hibernate 无法识别为映射类)。 name:指定实体对应的数据库表名(默认值为实体类名,如类名 Log 对应表 log)。
@Table 补充表级配置(可选,与 @Entity 配合使用)。 - name:同 @Entityname,优先级更高; - catalog:指定数据库 catalog; - schema:指定数据库 schema; - uniqueConstraints:定义表的唯一约束(如 @UniqueConstraint(columnNames = "requestUri"))。

主键相关注解

主键是实体映射的核心,Hibernate 通过 @Id 标识主键字段,并通过 @GeneratedValue 指定主键生成策略,满足不同业务场景(如自增、UUID、序列等)。

(1)@Id
  • 作用:标识当前字段为数据库表的主键(必须添加,一个实体类只能有一个 @Id)。
  • 位置:可放在属性上或 getter 方法上(需保持全类一致,不能混合使用)。
(2)@GeneratedValue
  • 作用:指定主键的生成策略(可选,若不指定则需手动设置主键值)。
  • 核心属性strategy,取值为 GenerationType 枚举,共 4 种策略:
策略类型 适用场景 注意事项
GenerationType.AUTO 默认策略,Hibernate 自动选择适合当前数据库的策略(如 MySQL 用自增,Oracle 用序列)。 灵活性高,但跨数据库时可能存在兼容性问题(需确保数据库支持对应策略)。
GenerationType.IDENTITY 依赖数据库的自增字段(如 MySQL 的 AUTO_INCREMENT、SQL Server 的 IDENTITY)。 仅支持有自增特性的数据库,且无法用于批量插入(Hibernate 需先获取自增 ID,导致批量 SQL 拆分)。
GenerationType.SEQUENCE 依赖数据库的序列(如 Oracle 的 SEQUENCE)。 需配合 @SequenceGenerator 定义序列信息(如下示例)。
GenerationType.TABLE 用一张中间表模拟序列生成主键(跨数据库兼容,但性能较差)。 需配合 @TableGenerator 定义中间表结构,一般不推荐生产环境使用。
(3)主键策略示例

以 Oracle 序列为例,演示 @SequenceGenerator@GeneratedValue 的配合使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Entity
@Table(name = "log") // 明确指定表名
public class Log {
// 主键字段
private long id;

// 定义序列生成器:名称为 "log_seq",对应数据库序列 "SEQ_LOG_ID"
@SequenceGenerator(
name = "log_seq", // 生成器名称(供 @GeneratedValue 引用)
sequenceName = "SEQ_LOG_ID", // 数据库中实际的序列名
allocationSize = 1 // 每次从序列获取的 ID 步长(默认 50,建议与序列步长一致)
)
@Id // 标识主键
@GeneratedValue(
strategy = GenerationType.SEQUENCE, // 策略为序列
generator = "log_seq" // 引用上面定义的序列生成器
)
@Column(name = "id", unique = true, nullable = false)
public long getId() {
return id;
}

// 其他 setter/getter 省略...
}

普通字段映射注解 @Column

用于配置实体类字段与数据库表列的映射关系(如列名、数据类型、约束等),是最常用的字段级注解。

常用属性说明
1
2
3
4
5
6
7
8
9
10
11
12
@Column(
name = "requestUri", // 数据库列名(默认与字段名一致,如字段 `requestUri` 对应列 `requestUri`)
length = 255, // 列长度(仅适用于字符串类型,如 VARCHAR(255))
nullable = true, // 是否允许为 NULL(默认 true,若设为 false 则添加 NOT NULL 约束)
unique = false, // 是否唯一(默认 false,若设为 true 则添加 UNIQUE 约束)
insertable = true, // 执行 INSERT 时是否包含该列(默认 true,若为 false 则 INSERT 语句不包含此列)
updatable = true, // 执行 UPDATE 时是否包含该列(默认 true,若为 false 则 UPDATE 语句不包含此列)
columnDefinition = "VARCHAR(255) COMMENT '请求URI'" // 自定义列的 SQL 定义(如注释、特殊类型)
)
public String getRequestUri() {
return requestUri;
}
特殊场景用法
  • 大文本字段:若字段是长文本(如日志详情),需用@Lob注解标识为 BLOB/CLOB 类型,配合@Column使用:

    1
    2
    3
    4
    5
    @Lob // 标识为大对象类型(MySQL 对应 TEXT,Oracle 对应 CLOB)
    @Column(name = "detail", columnDefinition = "TEXT COMMENT '日志详情'")
    public String getDetail() {
    return detail;
    }
  • 时间字段:若需指定时间精度,可通过columnDefinition控制(如DATETIME(3)保留毫秒):

    1
    2
    3
    4
    @Column(name = "create_time", columnDefinition = "DATETIME(3) COMMENT '创建时间'")
    public LocalDateTime getCreateTime() {
    return createTime;
    }

关联关系映射注解

在实际业务中,实体间通常存在一对一、一对多、多对一、多对多四种关联关系。Hibernate 提供了专门的注解来配置这些关系,核心是通过 @JoinColumn 指定关联的外键列。

1. 多对一(Many-to-One)

最常见的关联关系(如「订单(Order)」关联「用户(User)」,多个订单属于一个用户)。

注解说明
  • @ManyToOne:标识当前实体是关联关系的「多端」(如 Order 是多端,User 是一端)。
  • @JoinColumn:指定外键列(多端表中存储一端的主键,如 Order 表中的 user_id 列)。
示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 多端实体:Order(多个订单属于一个用户)
@Entity
@Table(name = "t_order")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// 关联用户(多对一)
@ManyToOne(fetch = FetchType.LAZY) // fetch:懒加载(默认 EAGER,建议优化为 LAZY)
@JoinColumn(
name = "user_id", // 外键列名(Order 表中的 user_id)
nullable = false, // 外键不允许为 NULL(即订单必须关联用户)
referencedColumnName = "id" // 关联一端(User)的主键列(默认是 id,可省略)
)
private User user; // 关联的一端实体

// 其他字段与 setter/getter 省略...
}

// 一端实体:User
@Entity
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;

// 其他字段与 setter/getter 省略...
}
关键注意事项
  • 懒加载优化@ManyToOne 默认是 FetchType.EAGER(立即加载),即查询 Order 时会自动查询关联的 User,可能导致性能问题。建议显式设置为 FetchType.LAZY(懒加载),仅在访问 order.getUser() 时才查询 User。
  • 级联操作:可通过 cascade 属性设置级联(如 cascade = CascadeType.PERSIST,保存 Order 时自动保存关联的 User),需根据业务场景谨慎使用(避免误删 / 误改数据)。

2. 一对多(One-to-Many)

与「多对一」是双向关联的两个视角(如「用户(User)」关联「订单(Order)」,一个用户有多个订单)。

注解说明
  • @OneToMany:标识当前实体是关联关系的「一端」(如 User 是一端,Order 是多端)。
  • mappedBy:指定关联关系的维护端(必须设置,否则会生成冗余的中间表)。mappedBy 的值是「多端实体中关联一端的字段名」(如 Order 中的 user 字段)。
示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 一端实体:User(一个用户有多个订单)
@Entity
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;

// 关联多个订单(一对多)
@OneToMany(
mappedBy = "user", // 维护端是 Order 的 user 字段(关键!避免冗余表)
fetch = FetchType.LAZY, // 懒加载(默认是 LAZY,查询 User 时不自动查询订单)
cascade = CascadeType.ALL // 级联所有操作(保存/更新/删除 User 时,同步操作关联的 Order)
)
// 可选:用 @OrderBy 指定订单的排序规则(如按创建时间降序)
@OrderBy("createTime DESC")
private List<Order> orders = new ArrayList<>(); // 关联的多端集合

// 其他字段与 setter/getter 省略...
}

// 多端实体:Order(同「多对一」示例,略)
关键注意事项
  • 关联维护端mappedBy 必须指向多端的关联字段(如 user),表示「由多端维护关联关系」(即外键 user_id 的更新由 Order 负责)。若不设置 mappedBy,Hibernate 会生成一张冗余的中间表(如 user_orders),导致数据不一致。
  • 集合初始化:建议初始化集合(如 new ArrayList<>()),避免访问 user.getOrders() 时出现 NullPointerException

3. 一对一(One-to-One)

适用于两个实体是「一一对应」的关系(如「用户(User)」与「用户详情(UserDetail)」),分为主键关联外键关联两种方式。

(1)主键关联(推荐)

两个实体的主键值完全一致(如 User 的 id=1 对应 UserDetail 的 id=1),无需额外外键列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 主实体:User
@Entity
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;

// 一对一关联 UserDetail
@OneToOne(
mappedBy = "user", // 维护端是 UserDetail 的 user 字段
cascade = CascadeType.ALL // 级联操作
)
private UserDetail detail;

// 省略 setter/getter...
}

// 关联实体:UserDetail
@Entity
@Table(name = "t_user_detail")
public class UserDetail {
@Id
@GeneratedValue(generator = "foreignGenerator")
// 配置主键生成器:引用 User 的主键
@GenericGenerator(
name = "foreignGenerator",
strategy = "foreign", // 策略为「外键关联」
parameters = @Parameter(name = "property", value = "user") // 关联的 User 字段
)
private Long id; // 与 User 的 id 一致
private String address;
private String phone;

// 一对一关联 User(维护端)
@OneToOne
@PrimaryKeyJoinColumn // 标识主键关联(即 id 既是主键也是外键)
private User user;

// 省略 setter/getter...
}
(2)外键关联

一个实体通过外键关联另一个实体的主键(如 UserDetail 中的 user_id 列关联 User 的 id),与多对一类似,但需添加 unique = true 保证唯一性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// UserDetail 实体(外键关联方式)
@Entity
@Table(name = "t_user_detail")
public class UserDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String address;

// 一对一关联 User(外键方式)
@OneToOne
@JoinColumn(
name = "user_id",
unique = true // 关键:外键唯一,保证一对一关系
)
private User user;

// 省略 setter/getter...
}

4. 多对多(Many-to-Many)

适用于两个实体是「多对多」关系(如「学生(Student)」与「课程(Course)」,一个学生选多门课,一门课有多个学生)。Hibernate 会自动生成中间表来维护关联关系。

示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 学生实体(多对多的一端)
@Entity
@Table(name = "t_student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

// 关联多门课程(多对多)
@ManyToMany(
cascade = CascadeType.PERSIST, // 级联保存(保存学生时保存关联的课程)
fetch = FetchType.LAZY
)
// 配置中间表信息
@JoinTable(
name = "student_course", // 中间表名(自定义)
joinColumns = @JoinColumn(name = "student_id"), // 当前实体(Student)在中间表的外键
inverseJoinColumns = @JoinColumn(name = "course_id") // 关联实体(Course)在中间表的外键
)
private List<Course> courses = new ArrayList<>();

// 省略 setter/getter...
}

// 课程实体(多对多的另一端)
@Entity
@Table(name = "t_course")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String courseName;

// 关联多个学生(多对多,反向关联)
@ManyToMany(
mappedBy = "courses", // 维护端是 Student 的 courses 字段(避免重复配置中间表)
fetch = FetchType.LAZY
)
private List<Student> students = new ArrayList<>();

// 省略 setter/getter...
}
关键注意事项
  • 中间表配置@JoinTable 仅需在「维护端」配置(如 Student),另一端通过 mappedBy 引用维护端的集合字段(如 courses),避免重复生成中间表。
  • 级联操作谨慎:多对多关系的级联操作(如 CascadeType.ALL)需特别谨慎,若删除一个学生时级联删除课程,可能误删其他学生关联的课程。

高级注解配置

除基础映射和关联关系外,Hibernate 注解还支持复合主键、枚举映射、继承关系等复杂场景。

1. 复合主键(Composite Primary Key)

当数据库表的主键由多个字段组成(如 order_id + product_id 作为订单详情表的主键)时,需使用复合主键注解。

实现步骤
  1. 创建复合主键类(必须实现 Serializable 接口,并重写 equals()hashCode() 方法);
  2. 在实体类中用 @IdClass@EmbeddedId 引用复合主键类。
示例:@IdClass 方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 1. 复合主键类(OrderDetail 的主键:orderId + productId)
public class OrderDetailPK implements Serializable {
private Long orderId; // 与 OrderDetail 的 orderId 字段名一致
private Long productId; // 与 OrderDetail 的 productId 字段名一致

// 必须重写 equals() 和 hashCode()(用于主键比较)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
OrderDetailPK that = (OrderDetailPK) o;
return Objects.equals(orderId, that.orderId) && Objects.equals(productId, that.productId);
}

@Override
public int hashCode() {
return Objects.hash(orderId, productId);
}

// 省略 setter/getter...
}

// 2. 实体类:OrderDetail(引用复合主键)
@Entity
@Table(name = "t_order_detail")
@IdClass(OrderDetailPK.class) // 指定复合主键类
public class OrderDetail {
// 复合主键的两个字段(需与 OrderDetailPK 的字段名完全一致)
@Id
@Column(name = "order_id")
private Long orderId;

@Id
@Column(name = "product_id")
private Long productId;

// 其他字段(如购买数量、单价)
private Integer quantity;
private BigDecimal price;

// 省略 setter/getter...
}

2. 枚举类型映射

若实体类字段是枚举类型(如订单状态 OrderStatus),Hibernate 提供 @Enumerated 注解将其映射到数据库表的字段(支持枚举的「名称」或「序号」映射)。

示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 枚举类:订单状态
public enum OrderStatus {
PENDING("待支付"), PAID("已支付"), SHIPPED("已发货"), FINISHED("已完成");

private final String desc;
OrderStatus(String desc) { this.desc = desc; }
// 省略 getter...
}

// 实体类:Order(枚举字段映射)
@Entity
@Table(name = "t_order")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// 枚举字段映射
@Enumerated(EnumType.STRING) // EnumType.STRING:存储枚举名称(如 "PENDING");EnumType.ORDINAL:存储序号(如 0)
@Column(name = "status", nullable = false)
private OrderStatus status; // 枚举字段

// 省略 setter/getter...
}
注意事项
  • 推荐使用 EnumType.STRINGEnumType.ORDINAL 存储枚举的序号(如 0、1、2),若后续枚举新增 / 删除元素,会导致序号对应关系错乱(如删除 PENDING 后,PAID 的序号从 1 变为 0,历史数据失效);EnumType.STRING 存储枚举名称(如 “PENDING”),兼容性更强。

注解方式的配置加载

使用注解后,Hibernate 无法通过 XML 映射文件识别实体类,需在 Configuration 中显式添加注解实体类,或通过「包扫描」批量加载。

1. 单个实体类加载

通过 addAnnotatedClass() 方法添加单个实体类(适用于实体类较少的场景):

1
2
3
4
5
6
7
8
9
// 构建 Hibernate 配置
Configuration config = new Configuration();
config.configure() // 加载 hibernate.cfg.xml 核心配置
.addAnnotatedClass(Log.class) // 添加 Log 注解实体
.addAnnotatedClass(User.class) // 添加 User 注解实体
.addAnnotatedClass(Order.class); // 添加 Order 注解实体

// 创建 SessionFactory
SessionFactory sessionFactory = config.buildSessionFactory();

2. 包扫描批量加载

若实体类较多,可通过 addPackage()setProperty() 配置包扫描(需 Hibernate 3.6+ 版本),批量加载指定包下的所有注解实体:

1
2
3
4
5
6
7
8
9
// 方式 1:通过 addPackage() 扫描包
Configuration config = new Configuration();
config.configure()
.addPackage("com.zhanghe.study.model"); // 扫描 com.zhanghe.study.model 包下的所有注解实体

// 方式 2:在 hibernate.cfg.xml 中配置包扫描(更推荐,无需硬编码)
// hibernate.cfg.xml 中添加:
<property name="hibernate.archive.autodetection">class,hbm</property>
<mapping package="com.zhanghe.study.model"/>

核心注意事项

  1. 注解位置一致性:注解可放在属性上或 getter 方法上,但全类必须保持一致(不能部分属性加注解,部分 getter 加注解),否则 Hibernate 会抛出映射异常。
  2. 懒加载的局限性:懒加载(FetchType.LAZY)仅在「Session 未关闭」时生效。若 Session 已关闭,访问懒加载字段(如 user.getOrders())会抛出 LazyInitializationException,需通过「延长 Session 生命周期」或「提前加载(如 fetch join 查询)」解决。
  3. 主键生成策略与数据库兼容性GenerationType.IDENTITY 仅支持 MySQL、SQL Server 等有自增特性的数据库;GenerationType.SEQUENCE 仅支持 Oracle、PostgreSQL 等有序列特性的数据库;跨数据库项目建议使用 GenerationType.AUTO
  4. 关联关系的维护端:一对多、多对多关系中,必须通过 mappedBy 指定维护端,避免生成冗余中间表或外键列。
  5. 级联操作的风险:级联操作(如 CascadeType.ALL)虽能简化代码,但可能导致误删 / 误改关联数据(如删除用户时级联删除所有订单),需根据业务场景谨慎选择级联类型(如仅用 CascadeType.PERSIST/CascadeType.MERGE

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10