服务台
现在让我们看看另一个常见的情况——多对多。假设我们在任务和员工之间有一个多对多的关系:
- 员工表中的一名员工可以执行任务表中的多项任务。
- 任务表中的一项任务可以分配给多个员工。
实体之间的这种关系称为多对多。并且为了在 SQL 级别实现它,我们需要一个额外的服务表。让我们称之为,例如,employee_task。
employee_task 表将只包含两列:
- 员工ID
- 任务编号
每次我们将特定任务分配给特定用户时,都会向该表中添加一个新行。例子:
员工ID | 任务编号 |
---|---|
1个 | 1个 |
1个 | 2个 |
2个 | 3个 |
那么,任务表应该会丢失其 employee_id 列。仅当任务只能分配给一名员工时才有意义。如果任务可以分配给几个员工,那么这个信息必须存储在employee_task 服务表中。
表级关系
这是我们的新表的样子:
ID | 姓名 | 职业 | 薪水 | 年龄 | 加入日期 |
---|---|---|---|---|---|
1个 | 伊万诺夫伊万 | 程序员 | 100000 | 25 | 2012-06-30 |
2个 | 彼得罗夫彼得 | 程序员 | 80000 | 23 | 2013-08-12 |
3个 | 伊万诺夫谢尔盖 | 测试员 | 40000 | 三十 | 2014-01-01 |
4个 | 拉比诺维奇·莫伊沙 | 导演 | 200000 | 35 | 2015-05-12 |
5个 | 基连科阿纳斯塔西娅 | 办公室主管 | 40000 | 25 | 2015-10-10 |
6个 | 瓦斯卡 | 猫 | 1000 | 3个 | 2018-11-11 |
员工表(未更改):
该表包含以下列:
- 标识符整数
- 名称VARCHAR
- 职业VARCHAR
- 薪水整数
- 年龄整数
- join_date日期
这就是任务表的样子,丢失了 employee_id 列(标记为红色):
ID | emploee_id | 姓名 | 最后期限 |
---|---|---|---|
1个 | 1个 | 修复一个前端bug | 2022-06-01 |
2个 | 2个 | 修复后端的一个bug | 2022-06-15 |
3个 | 5个 | 买咖啡 | 2022-07-01 |
4个 | 5个 | 买咖啡 | 2022-08-01 |
5个 | 5个 | 买咖啡 | 2022-09-01 |
6个 | (无效的) | 打扫办公室 | (无效的) |
7 | 4个 | 享受生活 | (无效的) |
8个 | 6个 | 享受生活 | (无效的) |
该表现在只有 3 列:
- id - 唯一的任务编号(和表中的行)
- employee_id -(已删除)
- name - 任务的名称和描述
- deadline - 任务必须完成的时间
我们还有employee_task 服务表,其中 employee_id 数据已从任务表迁移:
员工ID | 任务编号 |
---|---|
1个 | 1个 |
2个 | 2个 |
5个 | 3个 |
5个 | 4个 |
5个 | 5个 |
(无效的) | 6个 |
4个 | 7 |
6个 | 8个 |
我特意把删除的列暂时保存在task表中,这样大家可以看到它的数据已经移动到employee_task表中了。
另一个重点是employee_task 表中的红线“(NULL) 6” 。我将其标记为红色,因为它不会出现在employee_task 表中。
如果任务 7 分配给用户 4,则 employee_task 表中应该有一行 (4, 7)。
如果任务 6 没有分配给任何人,那么 employee_task 表中就没有它的记录。这些表的最终版本如下所示:
任务表:
ID | 姓名 | 最后期限 |
---|---|---|
1个 | 修复一个前端bug | 2022-06-01 |
2个 | 修复后端的一个bug | 2022-06-15 |
3个 | 买咖啡 | 2022-07-01 |
4个 | 买咖啡 | 2022-08-01 |
5个 | 买咖啡 | 2022-09-01 |
6个 | 打扫办公室 | (无效的) |
7 | 享受生活 | (无效的) |
8个 | 享受生活 | (无效的) |
employee_task 表:
员工ID | 任务编号 |
---|---|
1个 | 1个 |
2个 | 2个 |
5个 | 3个 |
5个 | 4个 |
5个 | 5个 |
4个 | 7 |
6个 | 8个 |
Java 类级别的通信
但是通过实体类级别的通信,我们有一个完整的订单。让我们从好消息开始吧。
首先,Hibernate 有一个特殊的@ManyToMany注解,可以让你很好地描述多对多表关系的情况。
其次,两个Entity类对我们来说还是足够了。我们不需要服务表的类。
这是我们的课程的样子。Employee类的原始形式:
@Entity
@Table(name="user")
class Employee {
@Column(name="id")
public Integer id;
@Column(name="name")
public String name;
@Column(name="occupation")
public String occupation;
@Column(name="salary")
public Integer salary;
@Column(name="join_date")
public Date join;
}
以及原始形式的EmployeeTask类:
@Entity
@Table(name="task")
class EmployeeTask {
@Column(name="id")
public Integer id;
@Column(name="name")
public String description;
@Column(name="deadline")
public Date deadline;
}
@ManyToMany 注解
我将省略示例中的现有字段,但我将添加新字段。这就是它们的样子。员工类:
@Entity
@Table(name="employee")
class Employee {
@Column(name="id")
public Integer id;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name="employee_task",
joinColumns= @JoinColumn(name="employee_id", referencedColumnName="id"),
inverseJoinColumns= @JoinColumn(name="task_id", referencedColumnName="id") )
private Set<EmployeeTask> tasks = new HashSet<EmployeeTask>();
}
和EmployeeTask类:
@Entity
@Table(name="task")
class EmployeeTask {
@Column(name="id")
public Integer id;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name="employee_task",
joinColumns= @JoinColumn(name="task_id", referencedColumnName="id"),
inverseJoinColumns= @JoinColumn(name=" employee_id", referencedColumnName="id") )
private Set<Employee> employees = new HashSet<Employee>();
}
看似一切都很复杂,其实一切都很简单。
首先,它使用@JoinTable注释(不要与 @JoinColumn 混淆),它描述了 employee_task 服务表。
其次,说明employee_task表的task_id列引用了task表的id列。
第三,它说 employee_task 表的 employee_id 列引用了 employee 表的 id 列。
事实上,在注释的帮助下,我们描述了 employee_task 表中包含哪些数据以及 Hibernate 应该如何解释它。
但现在我们可以非常轻松地向任何员工添加(和删除)任务。并且还将任何执行者添加到任何任务。
请求示例
让我们写几个有趣的查询来更好地理解这些 ManyToMany 字段是如何工作的。他们完全按照预期工作。
首先,我们的旧代码无需更改即可运行,因为 director 之前有一个 tasks 字段:
EmployeeTask task1 = new EmployeeTask();
task1.description = "Do Something Important";
session.persist(task1);
EmployeeTask task2 = new EmployeeTask();
task2.description = "Nothing to do";
session.persist(task2);
session.flush();
Employee director = session.find(Employee.class, 4);
director.tasks.add(task1);
director.tasks.add(task2);
session.update(director);
session.flush();
其次,如果我们想为某个任务分配另一个执行者,那么这样做就更容易了:
Employee director = session.find(Employee.class, 4);
EmployeeTask task = session.find(EmployeeTask.class, 101);
task.employees.add(director);
session.update(task);
session.flush();
重要的!执行这个请求的结果,不仅任务会有一个executor-director,而且director也会有101号任务。
首先,employee_task 表中有关主管和任务之间关系的事实将存储为一个字符串:(4,101)。
其次, @ManyToMany注解标记的字段是代理对象,访问时总是执行一次数据库查询。
因此,如果您向员工添加任务并将有关员工的信息保存到数据库中,那么此后该任务将在执行者列表中有一个新的执行者。