1. 简介
本文将快速介绍如何使用 @ManyToMany
注解在 Hibernate 中定义多对多关系。对于有经验的开发者来说,这是处理复杂实体关联的必备技能,直接上干货。
2. 典型示例
先看一个简单的实体关系图(ER图),展示了 employee
和 project
两个实体间的多对多关联:
在这个场景中:
- ✅ 一个员工可以参与多个项目
- ✅ 一个项目可以有多个员工参与
- ❌ 这就形成了典型的多对多关系
数据库设计需要三个表:
employee
表(主键employee_id
)project
表(主键project_id
)- 关联表
employee_project
(包含两个外键)
3. 数据库设置
假设已创建名为 spring_hibernate_many_to_many
的数据库。执行以下 SQL 创建表结构:
CREATE TABLE `employee` (
`employee_id` int(11) NOT NULL AUTO_INCREMENT,
`first_name` varchar(50) DEFAULT NULL,
`last_name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`employee_id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
CREATE TABLE `project` (
`project_id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(50) DEFAULT NULL,
PRIMARY KEY (`project_id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
CREATE TABLE `employee_project` (
`employee_id` int(11) NOT NULL,
`project_id` int(11) NOT NULL,
PRIMARY KEY (`employee_id`,`project_id`),
KEY `project_id` (`project_id`),
CONSTRAINT `employee_project_ibfk_1`
FOREIGN KEY (`employee_id`) REFERENCES `employee` (`employee_id`),
CONSTRAINT `employee_project_ibfk_2`
FOREIGN KEY (`project_id`) REFERENCES `project` (`project_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
⚠️ 注意:关联表必须使用复合主键(employee_id
+ project_id
)确保关系唯一性。
4. 模型类
使用 JPA 注解创建 Employee
和 Project
实体类:
Employee.java
@Entity
@Table(name = "Employee")
public class Employee {
// ...
@ManyToMany(cascade = { CascadeType.ALL })
@JoinTable(
name = "Employee_Project",
joinColumns = { @JoinColumn(name = "employee_id") },
inverseJoinColumns = { @JoinColumn(name = "project_id") }
)
Set<Project> projects = new HashSet<>();
// standard constructor/getters/setters
}
Project.java
@Entity
@Table(name = "Project")
public class Project {
// ...
@ManyToMany(mappedBy = "projects")
private Set<Employee> employees = new HashSet<>();
// standard constructors/getters/setters
}
关键注解解析
@ManyToMany
- 标记多对多关系
- 双向关联:两端都需配置
- 级联操作:
Employee
端配置CascadeType.ALL
**
@JoinTable
**(关系维护端)- 指定关联表名称
joinColumns
:当前实体在关联表中的外键inverseJoinColumns
:对方实体的外键
**
mappedBy
**(被维护端)- 指定由
Employee
的projects
字段维护关系 - 避免重复生成关联表
- 指定由
⚠️ 踩坑提示:双向关联必须正确设置 mappedBy
,否则会导致数据不一致问题。
5. 执行测试
通过 JUnit 测试验证多对多关系:
public class HibernateManyToManyAnnotationMainIntegrationTest {
private static SessionFactory sessionFactory;
private Session session;
//...
@Test
public void givenSession_whenRead_thenReturnsMtoMdata() {
prepareData();
@SuppressWarnings("unchecked")
List<Employee> employeeList = session.createQuery("FROM Employee").list();
@SuppressWarnings("unchecked")
List<Project> projectList = session.createQuery("FROM Project").list();
assertNotNull(employeeList);
assertNotNull(projectList);
assertEquals(2, employeeList.size());
assertEquals(2, projectList.size());
for(Employee employee : employeeList) {
assertNotNull(employee.getProjects());
assertEquals(2, employee.getProjects().size());
}
for(Project project : projectList) {
assertNotNull(project.getEmployees());
assertEquals(2, project.getEmployees().size());
}
}
private void prepareData() {
String[] employeeData = { "Peter Oven", "Allan Norman" };
String[] projectData = { "IT Project", "Networking Project" };
Set<Project> projects = new HashSet<Project>();
for (String proj : projectData) {
projects.add(new Project(proj));
}
for (String emp : employeeData) {
Employee employee = new Employee(emp.split(" ")[0], emp.split(" ")[1]);
employee.setProjects(projects);
for (Project proj : projects) {
proj.getEmployees().add(employee);
}
session.persist(employee);
}
}
//...
}
测试验证点:
- ✅ 创建 2 个员工和 2 个项目
- ✅ 每个员工关联所有项目
- ✅ 每个项目包含所有员工
- ✅ 双向关联数据一致性
6. 总结
Hibernate 的 @ManyToMany
注解提供了一种简洁高效的多对多关系映射方案,相比传统的 XML 配置方式更加直观。关键点总结:
核心注解
@ManyToMany
:声明多对多关系@JoinTable
:定义关联表结构mappedBy
:指定关系维护方
最佳实践
- 使用
Set
而非List
避免重复数据 - 双向关联必须正确配置
mappedBy
- 合理使用级联操作
- 使用
常见陷阱
- ❌ 忘记初始化集合(
new HashSet<>()
) - ❌ 双向关联只配置一端
- ❌ 关联表主键配置错误
- ❌ 忘记初始化集合(
完整代码示例可在 GitHub 获取。实际开发中,建议结合 Spring Data JPA 进一步简化操作。