
本教程详细阐述了在 hibernate 中如何正确映射自引用多对多关系,以处理同一实体(如 `test`)之间的复杂父子关联。通过利用 `@manytomany` 和 `@jointable` 注解,我们将展示如何在一个中间关联表(`relation`)中双向定义父集合和子集合,从而实现灵活且高效的数据模型。
在复杂的业务场景中,我们经常会遇到实体之间存在自引用关系的情况,例如组织架构中的上下级关系、社交网络中的关注关系,或者本文将讨论的通用父子关系。当这种自引用关系是多对多时(一个父节点可以有多个子节点,一个子节点也可以有多个父节点),如何在 Hibernate 中进行准确且高效的映射,是许多开发者面临的挑战。本教程将以 Test 实体为例,详细讲解如何通过 @ManyToMany 和 @JoinTable 注解,在一个中间关联表中实现自引用多对多关系的双向映射。
数据库模型解析
首先,我们来看一下涉及的数据库表结构。我们有两个核心表:test_table 和 relation。
-
test_table (主实体表)
- id: 主键,自增,非空。
- comment: 描述信息。
这是一个典型的实体表,代表了我们系统中需要建立父子关联的独立单元。
-
relation (中间关联表)
- id: 主键,自增。
- a_id: 外键,关联到 test_table 的 id 列,并存在索引。
- a_parent_id: 外键,关联到 test_table 的 id 列,并存在索引。
- (a_id, a_parent_id): 联合唯一约束,确保一对父子关系不会重复。
relation 表是实现多对多关系的关键。a_id 和 a_parent_id 共同定义了一个父子对。例如,如果 a_id = 3 且 a_parent_id = 1,则表示 Test 实体 id 为 3 的是 Test 实体 id 为 1 的子节点。
Hibernate 实体映射实践
现在,我们将 test_table 映射到 Hibernate 的 Test 实体。目标是让 Test 实体能够方便地访问其所有父节点和子节点。
基础 Test 实体结构
首先是 Test 实体的基本映射:
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name = "test_table")
public class Test {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false)
private Long id;
@Column
private String comment;
// 父节点集合将在此处定义
private List parents;
// 子节点集合将在此处定义
private List children;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public List getParents() {
return parents;
}
public void setParents(List parents) {
this.parents = parents;
}
public List getChildren() {
return children;
}
public void setChildren(List children) {
this.children = children;
}
} 映射父节点集合 (parents)
要映射一个 Test 实例的所有父节点,我们需要告诉 Hibernate 如何通过 relation 表找到它们。对于一个 Test 实例(比如 id=X),其父节点是 relation 表中 a_id 列为 X 的记录对应的 a_parent_id。
@ManyToMany(targetEntity = Test.class)
@JoinTable(
name = "relation", // 中间关联表的名称
joinColumns = @JoinColumn(name = "a_id", referencedColumnName = "id"), // 当前实体(Test)在关联表中的外键列
inverseJoinColumns = @JoinColumn(name = "a_parent_id", referencedColumnName = "id") // 关联实体(Parent Test)在关联表中的外键列
)
private List parents; - @ManyToMany(targetEntity = Test.class): 指明这是一个多对多关系,并且关联的目标实体是自身 (Test.class),这定义了自引用特性。
- @JoinTable(name = "relation"): 指定了用于维护此多对多关系的中间表是 relation。
- joinColumns = @JoinColumn(name = "a_id", referencedColumnName = "id"):
- name = "a_id": 表示在 relation 表中,a_id 列是当前 Test 实体(即子节点)的 ID。
- referencedColumnName = "id": 表示 a_id 列引用的是 Test 实体(test_table)的 id 列。
- inverseJoinColumns = @JoinColumn(name = "a_parent_id", referencedColumnName = "id"):
- name = "a_parent_id": 表示在 relation 表中,a_parent_id 列是关联的父 Test 实体(即父节点)的 ID。
- referencedColumnName = "id": 表示 a_parent_id 列引用的是 Test 实体(test_table)的 id 列。
映射子节点集合 (children)
映射一个 Test 实例的所有子节点与映射父节点类似,但 joinColumns 和 inverseJoinColumns 的角色需要互换。对于一个 Test 实例(比如 id=Y),其子节点是 relation 表中 a_parent_id 列为 Y 的记录对应的 a_id。
@ManyToMany(targetEntity = Test.class)
@JoinTable(
name = "relation", // 中间关联表的名称
joinColumns = @JoinColumn(name = "a_parent_id", referencedColumnName = "id"), // 当前实体(Test)在关联表中的外键列
inverseJoinColumns = @JoinColumn(name = "a_id", referencedColumnName = "id") // 关联实体(Child Test)在关联表中的外键列
)
private List children; - joinColumns = @JoinColumn(name = "a_parent_id", referencedColumnName = "id"):
- name = "a_parent_id": 表示在 relation 表中,a_parent_id 列是当前 Test 实体(即父节点)的 ID。
- referencedColumnName = "id": 表示 a_parent_id 列引用的是 Test 实体(test_table)的 id 列。
- inverseJoinColumns = @JoinColumn(name = "a_id", referencedColumnName = "id"):
- name = "a_id": 表示在 relation 表中,a_id 列是关联的子 Test 实体(即子节点)的 ID。
- referencedColumnName = "id": 表示 a_id 列引用的是 Test 实体(test_table)的 id 列。
通过这种方式,我们有效地“翻转”了 joinColumns 和 inverseJoinColumns 的定义,从而能够从同一个 relation 表中以两种不同的视角(获取父或获取子)来解析关系。
完整 Test 实体代码示例
import javax.persistence.*;
import java.util.List;
import java.util.ArrayList; // 建议初始化列表以避免NullPointerException
@Entity
@Table(name = "test_table")
public class Test {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false)
private Long id;
@Column
private String comment;
@ManyToMany(targetEntity = Test.class)
@JoinTable(
name = "relation",
joinColumns = @JoinColumn(name = "a_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "a_parent_id", referencedColumnName = "id")
)
private List parents = new ArrayList<>(); // 建议初始化
@ManyToMany(targetEntity = Test.class)
@JoinTable(
name










