服務台

現在讓我們看看另一個常見的情況——多對多。假設我們在任務和員工之間有一個多對多的關係:

  • 員工表中的一名員工可以執行任務表中的多項任務。
  • 任務表中的一項任務可以分配給多個員工。

實體之間的這種關係稱為多對多。並且為了在 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();

重要的!作為執行這個請求的結果,不僅任務會有一個執行者-director,director 也會有 101 號任務。

首先,employee_task 表中有關主管和任務之間關係的事實將存儲為一個字符串:(4,101)。

其次, @ManyToMany註解標記的字段是代理對象,訪問時總是執行一次數據庫查詢。

因此,如果您向員工添加任務並將有關員工的信息保存到數據庫中,那麼此後該任務將在執行者列表中有一個新的執行者。