@ManyToMany

可用

服务台

现在让我们看看另一个常见的情况——多对多。假设我们在任务和员工之间有一个多对多的关系:

  • 员工表中的一名员工可以执行任务表中的多项任务。
  • 任务表中的一项任务可以分配给多个员工。

实体之间的这种关系称为多对多。并且为了在 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注解标记的字段是代理对象,访问时总是执行一次数据库查询。

因此,如果您向员工添加任务并将有关员工的信息保存到数据库中,那么此后该任务将在执行者列表中有一个新的执行者。

评论
  • 受欢迎
你必须先登录才能发表评论
此页面还没有任何评论