服務台
現在讓我們看看另一個常見的情況——多對多。假設我們在任務和員工之間有一個多對多的關係:
- 員工表中的一名員工可以執行任務表中的多項任務。
- 任務表中的一項任務可以分配給多個員工。
實體之間的這種關係稱為多對多。並且為了在 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註解標記的字段是代理對象,訪問時總是執行一次數據庫查詢。
因此,如果您向員工添加任務並將有關員工的信息保存到數據庫中,那麼此後該任務將在執行者列表中有一個新的執行者。