サービステーブル
次に、別の一般的なケースである多対多を見てみましょう。タスクと従業員の間に多対多の関係があると想像してみましょう。
- 従業員テーブル内の 1 人の従業員は、タスク テーブルから多くのタスクを実行できます。
- タスク テーブル内の 1 つのタスクを複数の従業員に割り当てることができます。
このエンティティ間の関係は多対多と呼ばれます。これを SQL レベルで実装するには、追加のサービス テーブルが必要です。たとえば、employee_task と名付けましょう。
employee_task テーブルには次の 2 つの列のみが含まれます。
- 従業員ID
- タスクID
特定のタスクを特定のユーザーに割り当てるたびに、このテーブルに新しい行が追加されます。例:
従業員ID | タスクID |
---|---|
1 | 1 |
1 | 2 |
2 | 3 |
さて、タスク テーブルでは、employee_id 列が失われるはずです。これは、タスクを 1 人の従業員にのみ割り当てることができる場合にのみ意味を持ちます。タスクを複数の従業員に割り当てることができる場合は、この情報をemployee_task サービス テーブルに保存する必要があります。
テーブルレベルの関係
新しいテーブルは次のようになります。
ID | 名前 | 職業 | 給料 | 年 | 参加日 |
---|---|---|---|---|---|
1 | イワノフ・イワン | プログラマー | 100000 | 25 | 2012-06-30 |
2 | ペトロフ・ペトル | プログラマー | 80000 | 23 | 2013-08-12 |
3 | イワノフ・セルゲイ | テスター | 40000 | 30 | 2014-01-01 |
4 | ラビノビッチ・モイシャ | 監督 | 200000 | 35 | 2015-05-12 |
5 | キリエンコ・アナスタシア | 事務長 | 40000 | 25 | 2015-10-10 |
6 | バスカ | 猫 | 1000 | 3 | 2018-11-11 |
従業員テーブル (変更なし) :
このテーブルには次の列があります。
- ID INT
- 名前VARCHAR
- 職業VARCHAR
- 給与INT
- 年齢INT
- 参加日DATE
タスク テーブルは次のようになります。employee_id 列(赤でマーク)が失われています。
ID | 従業員ID | 名前 | 締め切り |
---|---|---|---|
1 | 1 | フロントエンドのバグを修正 | 2022-06-01 |
2 | 2 | バックエンドのバグを修正 | 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 - 一意のタスク番号 (およびテーブル内の行)
- 従業員 ID - (削除されました)
- name - タスクの名前と説明
- 期限- タスクを完了しなければならない期限
また、 employee_task サービス テーブルもあります。ここには、employee_id データがタスク テーブルから移行されています。
従業員ID | タスクID |
---|---|
1 | 1 |
2 | 2 |
5 | 3 |
5 | 4 |
5 | 5 |
(ヌル) | 6 |
4 | 7 |
6 | 8 |
削除された列を意図的にタスク テーブルに一時的に保存し、そこからのデータがemployee_task テーブルに移動したことがわかるようにしました。
もう 1 つの重要な点は、 employee_task テーブルの赤い線「(NULL) 6」です。これは、 employee_task テーブルには含まれないため、赤でマークしました。
タスク 7 がユーザー 4 に割り当てられている場合、employee_task テーブルには行 (4, 7) が存在するはずです。
タスク 6 が誰にも割り当てられていない場合、employee_task テーブルにはそのタスクのレコードが存在しません。これらのテーブルの最終バージョンは次のようになります。
タスクテーブル:
ID | 名前 | 締め切り |
---|---|---|
1 | フロントエンドのバグを修正 | 2022-06-01 |
2 | バックエンドのバグを修正 | 2022-06-15 |
3 | コーヒーを買う | 2022-07-01 |
4 | コーヒーを買う | 2022-08-01 |
5 | コーヒーを買う | 2022-09-01 |
6 | オフィスを掃除する | (ヌル) |
7 | 人生を楽しむ | (ヌル) |
8 | 人生を楽しむ | (ヌル) |
従業員_タスクテーブル:
従業員ID | タスクID |
---|---|
1 | 1 |
2 | 2 |
5 | 3 |
5 | 4 |
5 | 5 |
4 | 7 |
6 | 8 |
Javaクラスレベルでの通信
しかし、エンティティクラスのレベルでの通信では、完全な順序が得られます。良いニュースから始めましょう。
まず、Hibernate には特別な@ManyToManyアノテーションがあり、これを使用して多対多のテーブル リレーションシップのケースを適切に説明できます。
次に、2 つの 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 サービス テーブルを記述します。
2 番目に、employee_task テーブルの task_id 列が task テーブルの id 列を参照していることが記述されています。
3 番目に、employee_task テーブルのemployee_id 列は、employee テーブルの id 列を参照していると述べています。
実際、アノテーションを利用して、employee_task テーブルにどのようなデータが含まれているか、そして Hibernate がそれをどのように解釈するかを説明しました。
しかし今では、どの従業員にもタスクを非常に簡単に追加 (および削除) できるようになりました。また、任意のタスクに任意の実行者を追加します。
リクエスト例
これらのManyToManyフィールドがどのように機能するかをより深く理解するために、いくつかの興味深いクエリを作成してみましょう。そして、それらはまさに期待どおりに機能します。
まず、director には以前タスク フィールドがあったため、古いコードは変更なしで機能します。
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();
重要!このリクエストを実行した結果、タスクに実行者兼ディレクターがいるだけでなく、ディレクターもタスク番号 101 を持つことになります。
まず、ディレクターとタスク間の関係に関する事実が、employee_task テーブルに文字列 (4,101) として保存されます。
次に、 @ManyToManyアノテーションが付けられたフィールドはプロキシ オブジェクトであり、アクセスされると常にデータベース クエリが実行されます。
したがって、タスクを従業員に追加し、その従業員に関する情報をデータベースに保存すると、その後、タスクの実行者のリストに新しい実行者が追加されます。