Stół serwisowy
Teraz spójrzmy na inny częsty przypadek — wiele do wielu. Wyobraźmy sobie, że mamy relację wiele do wielu między zadaniami a pracownikami :
- Jeden pracownik w tabeli pracowników może wykonywać wiele zadań z tabeli zadań.
- Jedno zadanie w tabeli zadań można przypisać kilku pracownikom.
Ta relacja między jednostkami nazywana jest wiele-do-wielu. A żeby to zaimplementować na poziomie SQL potrzebujemy dodatkowej tabeli usług. Nazwijmy to na przykład pracownik_zadanie.
Tabela pracownik_zadanie będzie zawierała tylko dwie kolumny:
- dowód pracownika
- identyfikator_zadania
Za każdym razem, gdy przypiszemy określone zadanie konkretnemu użytkownikowi, do tej tabeli zostanie dodany nowy wiersz. Przykład:
dowód pracownika | identyfikator_zadania |
---|---|
1 | 1 |
1 | 2 |
2 | 3 |
Cóż, tabela zadań powinna utracić swoją kolumnę id_pracownika . Ma to sens tylko wtedy, gdy zadanie można przypisać tylko jednemu pracownikowi. Jeśli zadanie można przypisać kilku pracownikom, to informacja ta musi być przechowywana w tabeli usługi pracownik_zadanie .
Relacja na poziomie tabeli
Oto jak będą wyglądać nasze nowe stoły:
ID | nazwa | zawód | wynagrodzenie | wiek | data_dołączenia |
---|---|---|---|---|---|
1 | Iwanow Iwan | Programista | 100000 | 25 | 2012-06-30 |
2 | Pietrow Pietr | Programista | 80000 | 23 | 2013-08-12 |
3 | Iwanow Siergiej | Próbnik | 40000 | trzydzieści | 2014-01-01 |
4 | Rabinowicz Mojsza | Dyrektor | 200000 | 35 | 2015-05-12 |
5 | Kirienko Anastazja | Kierownik biura | 40000 | 25 | 2015-10-10 |
6 | Vaska | Kot | 1000 | 3 | 2018-11-11 |
Tabela pracowników ( bez zmian ) :
Ta tabela ma następujące kolumny:
- identyfikator INT
- imię VARCHAR
- zawód VARCHAR
- wynagrodzenie INT
- wiek INT
- data_dołączenia DATA
A tak wygląda tabela zadań , zgubiona kolumna id_pracownika (zaznaczona na czerwono):
ID | identyfikator_pracownika | nazwa | termin ostateczny |
---|---|---|---|
1 | 1 | Napraw błąd w interfejsie użytkownika | 2022-06-01 |
2 | 2 | Napraw błąd w backendzie | 2022-06-15 |
3 | 5 | Kup kawę | 2022-07-01 |
4 | 5 | Kup kawę | 2022-08-01 |
5 | 5 | Kup kawę | 2022-09-01 |
6 | (ZERO) | Posprzątaj biuro | (ZERO) |
7 | 4 | Ciesz się życiem | (ZERO) |
8 | 6 | Ciesz się życiem | (ZERO) |
Ta tabela ma teraz tylko 3 kolumny:
- id - unikalny numer zadania (i wierszy w tabeli)
- id_pracownika - (usunięto)
- name - nazwa i opis zadania
- termin – czas, do którego zadanie musi zostać wykonane
Mamy również tabelę usługi Employee_task , w której dane o identyfikatorze_pracownika zostały przeniesione z tabeli zadań:
dowód pracownika | identyfikator_zadania |
---|---|
1 | 1 |
2 | 2 |
5 | 3 |
5 | 4 |
5 | 5 |
(ZERO) | 6 |
4 | 7 |
6 | 8 |
Celowo tymczasowo zapisałem usuniętą kolumnę w tabeli zadań, abyś mógł zobaczyć, że dane z niej zostały przeniesione do tabeli pracownik_zadanie.
Kolejnym ważnym punktem jest czerwona linia „(NULL) 6” w tabeli pracownik_zadanie. Zaznaczyłem go na czerwono, ponieważ nie będzie go w tabeli Employee_task .
Jeśli zadanie 7 jest przypisane do użytkownika 4, to w tabeli pracownik_zadanie powinien znajdować się wiersz (4, 7).
Jeśli zadanie 6 nie zostanie nikomu przydzielone, po prostu nie będzie dla niego żadnego rekordu w tabeli pracownik_zadanie. Oto jak będą wyglądać ostateczne wersje tych tabel:
tabela zadań :
ID | nazwa | termin ostateczny |
---|---|---|
1 | Napraw błąd w interfejsie użytkownika | 2022-06-01 |
2 | Napraw błąd w backendzie | 2022-06-15 |
3 | Kup kawę | 2022-07-01 |
4 | Kup kawę | 2022-08-01 |
5 | Kup kawę | 2022-09-01 |
6 | Posprzątaj biuro | (ZERO) |
7 | Ciesz się życiem | (ZERO) |
8 | Ciesz się życiem | (ZERO) |
tabela_zadanie_pracownika:
dowód pracownika | identyfikator_zadania |
---|---|
1 | 1 |
2 | 2 |
5 | 3 |
5 | 4 |
5 | 5 |
4 | 7 |
6 | 8 |
Komunikacja na poziomie klasy Java
Ale z komunikacją na poziomie klas Entity mamy kompletny porządek. Zacznijmy od dobrych wiadomości.
Po pierwsze, Hibernate ma specjalną adnotację @ManyToMany , która pozwala dobrze opisać przypadek relacji tabel wiele-do-wielu.
Po drugie, wciąż wystarczą nam dwie klasy Entity. Nie potrzebujemy klasy dla stołu serwisowego.
Oto jak będą wyglądać nasze zajęcia. Klasa Pracownik w oryginalnej formie:
@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;
}
I klasa EmployeeTask w oryginalnej postaci:
@Entity
@Table(name="task")
class EmployeeTask {
@Column(name="id")
public Integer id;
@Column(name="name")
public String description;
@Column(name="deadline")
public Date deadline;
}
Adnotacja @ManyToMany
W przykładach pominę istniejące pola, ale dodam nowe. Oto jak będą wyglądać. Klasa pracownika :
@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>();
}
I klasa 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>();
}
Wydaje się, że wszystko jest skomplikowane, ale w rzeczywistości wszystko jest proste.
Po pierwsze, używa adnotacji @JoinTable (nie mylić z @JoinColumn), która opisuje tabelę usług Employee_task.
Po drugie, opisuje, że kolumna id_zadania w tabeli pracownik_zadanie odwołuje się do kolumny id w tabeli zadań.
Po trzecie, mówi, że kolumna id_pracownika w tabeli pracownik_zadanie odnosi się do kolumny id w tabeli pracownik.
Tak naprawdę za pomocą adnotacji opisaliśmy, jakie dane są zawarte w tabeli pracownik_zadanie i jak Hibernate powinien je interpretować.
Ale teraz możemy bardzo łatwo dodać (i usunąć) zadanie dla dowolnego pracownika. A także dodaj dowolnego wykonawcę do dowolnego zadania.
Poproś o przykłady
Napiszmy kilka interesujących zapytań, aby lepiej zrozumieć, jak działają te pola ManyToMany. I działają dokładnie tak, jak oczekiwano.
Po pierwsze, nasz stary kod będzie działał bez zmian, ponieważ wcześniej dyrektor miał pole zadań:
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();
Po drugie, jeśli chcemy przypisać do jakiegoś zadania innego wykonawcę, to jeszcze prościej jest to zrobić:
Employee director = session.find(Employee.class, 4);
EmployeeTask task = session.find(EmployeeTask.class, 101);
task.employees.add(director);
session.update(task);
session.flush();
Ważny! W wyniku realizacji tego zlecenia nie tylko zadanie będzie miało wykonawcę-kierownika, ale także dyrektor będzie miał zadanie nr 101.
Po pierwsze, fakt o relacji między dyrektorem a zadaniem w tabeli pracownik_zadanie zostanie zapisany jako ciąg znaków: (4,101).
Po drugie, pola oznaczone adnotacjami @ManyToMany są obiektami proxy, a kiedy uzyskuje się do nich dostęp, zawsze wykonywane jest zapytanie do bazy danych.
Jeśli więc dodasz zadanie do pracownika i zapiszesz informacje o pracowniku do bazy danych, to zadanie będzie miało nowego wykonawcę na liście wykonawców.