Masa de serviciu
Acum să ne uităm la un alt caz obișnuit - de la mulți la mulți. Să ne imaginăm că avem o relație de la mulți la mulți între sarcini și angajați :
- Un angajat din tabelul de angajați poate face multe sarcini din tabelul de sarcini.
- O sarcină din tabelul de sarcini poate fi atribuită mai multor angajați.
Această relație între entități se numește multi-la-mulți. Și pentru a-l implementa la nivel SQL, avem nevoie de un tabel de servicii suplimentar. Să-i numim, de exemplu, angajat_sarcina.
Tabelul employee_task va conține doar două coloane:
- card de identitate al angajatului
- task_id
De fiecare dată când atribuim o anumită sarcină unui anumit utilizator, un nou rând va fi adăugat la acest tabel. Exemplu:
card de identitate al angajatului | task_id |
---|---|
1 | 1 |
1 | 2 |
2 | 3 |
Ei bine, tabelul de activități ar trebui să-și piardă coloana employee_id . Are sens doar dacă sarcina poate fi atribuită unui singur angajat. Dacă sarcina poate fi atribuită mai multor angajați, atunci aceste informații trebuie să fie stocate în tabelul de serviciu employee_task .
Relația la nivel de masă
Iată cum vor arăta noile noastre tabele:
id | Nume | ocupaţie | salariu | vârstă | Data înscrierii |
---|---|---|---|---|---|
1 | Ivanov Ivan | Programator | 100000 | 25 | 30-06-2012 |
2 | Petrov Petr | Programator | 80000 | 23 | 12-08-2013 |
3 | Ivanov Serghei | Tester | 40000 | treizeci | 01-01-2014 |
4 | Rabinovici Moisha | Director | 200000 | 35 | 2015-05-12 |
5 | Kirienko Anastasia | Manager de birou | 40000 | 25 | 2015-10-10 |
6 | Vaska | Pisică | 1000 | 3 | 2018-11-11 |
Tabelul angajaților ( neschimbat ) :
Acest tabel are următoarele coloane:
- id INT
- nume VARCHAR
- ocupatie VARCHAR
- salariu INT
- vârsta INT
- join_date DATE
Și așa arată tabelul de activități , a pierdut coloana employee_id (marcată cu roșu):
id | Emploee_id | Nume | Termen limită |
---|---|---|---|
1 | 1 | Remediați o eroare pe front-end | 2022-06-01 |
2 | 2 | Remediați o eroare pe backend | 2022-06-15 |
3 | 5 | Cumpără cafea | 2022-07-01 |
4 | 5 | Cumpără cafea | 2022-08-01 |
5 | 5 | Cumpără cafea | 2022-09-01 |
6 | (NUL) | Curățați biroul | (NUL) |
7 | 4 | Bucură-te de viață | (NUL) |
8 | 6 | Bucură-te de viață | (NUL) |
Acest tabel are acum doar 3 coloane:
- id - numărul unic al sarcinii (și rândurile din tabel)
- employee_id - (eliminat)
- nume - numele și descrierea sarcinii
- termen - timpul până la care sarcina trebuie finalizată
Avem, de asemenea, tabelul de serviciu employee_task , unde datele employee_id au migrat din tabelul de activități:
card de identitate al angajatului | task_id |
---|---|
1 | 1 |
2 | 2 |
5 | 3 |
5 | 4 |
5 | 5 |
(NUL) | 6 |
4 | 7 |
6 | 8 |
Am salvat temporar coloana ștearsă în tabelul de activități, astfel încât să puteți vedea că datele din aceasta au fost mutate în tabelul employee_task.
Un alt punct important este linia roșie „(NULL) 6” din tabelul employee_task. L-am marcat cu roșu pentru că nu va fi în tabelul employee_task .
Dacă sarcina 7 este atribuită utilizatorului 4, atunci ar trebui să existe un rând (4, 7) în tabelul employee_task.
Dacă sarcina 6 nu este atribuită nimănui, atunci pur și simplu nu va exista nicio înregistrare pentru ea în tabelul employee_task. Iată cum vor arăta versiunile finale ale acestor tabele:
tabel de sarcini :
id | Nume | Termen limită |
---|---|---|
1 | Remediați o eroare pe front-end | 2022-06-01 |
2 | Remediați o eroare pe backend | 2022-06-15 |
3 | Cumpără cafea | 2022-07-01 |
4 | Cumpără cafea | 2022-08-01 |
5 | Cumpără cafea | 2022-09-01 |
6 | Curățați biroul | (NUL) |
7 | Bucură-te de viață | (NUL) |
8 | Bucură-te de viață | (NUL) |
tabelul task_empleat:
card de identitate al angajatului | task_id |
---|---|
1 | 1 |
2 | 2 |
5 | 3 |
5 | 4 |
5 | 5 |
4 | 7 |
6 | 8 |
Comunicare la nivel de clasă Java
Dar cu comunicarea la nivel de Entitate-clase, avem o comandă completă. Să începem cu veștile bune.
În primul rând, Hibernate are o adnotare specială @ManyToMany care vă permite să descrieți bine cazul unei relații de tabel multi-la-mulți.
În al doilea rând, două clase de Entități sunt încă suficiente pentru noi. Nu avem nevoie de o clasă pentru masa de serviciu.
Iată cum vor arăta cursurile noastre. Clasa de angajați în forma sa originală:
@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 clasa EmployeeTask în forma sa originală:
@Entity
@Table(name="task")
class EmployeeTask {
@Column(name="id")
public Integer id;
@Column(name="name")
public String description;
@Column(name="deadline")
public Date deadline;
}
Adnotare @ManyToMany
Voi omite câmpurile existente în exemple, dar voi adăuga altele noi. Iată cum vor arăta. Clasa de angajati :
@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 clasa 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>();
}
Se pare că totul este complicat, dar de fapt totul este simplu.
În primul rând, folosește adnotarea @JoinTable (a nu se confunda cu @JoinColumn), care descrie tabelul de serviciu employee_task.
În al doilea rând, descrie faptul că coloana task_id a tabelului employee_task se referă la coloana id a tabelului task.
În al treilea rând, spune că coloana employee_id din tabelul employee_task se referă la coloana id din tabelul employee.
De fapt, cu ajutorul adnotărilor, am descris ce date sunt conținute în tabelul employee_task și cum ar trebui să le interpreteze Hibernate.
Dar acum putem adăuga (și șterge) foarte ușor o sarcină oricărui angajat. Și, de asemenea, adăugați orice interpret la orice sarcină.
Cere exemple
Să scriem câteva interogări interesante pentru a înțelege mai bine cum funcționează aceste câmpuri ManyToMany. Și funcționează exact așa cum era de așteptat.
În primul rând, vechiul nostru cod va funcționa fără modificări, deoarece directorul avea înainte un câmp de sarcini:
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();
În al doilea rând, dacă vrem să atribuim un alt executant unei anumite sarcini, atunci este și mai ușor să facem acest lucru:
Employee director = session.find(Employee.class, 4);
EmployeeTask task = session.find(EmployeeTask.class, 101);
task.employees.add(director);
session.update(task);
session.flush();
Important! Ca urmare a executării acestei solicitări, nu numai sarcina va avea un executor-director, ci și directorul va avea sarcina nr. 101.
În primul rând, faptul despre relația dintre director și sarcina din tabelul employee_task va fi stocat ca șir: (4,101).
În al doilea rând, câmpurile marcate cu adnotări @ManyToMany sunt obiecte proxy, iar atunci când sunt accesate, este întotdeauna executată o interogare a bazei de date.
Deci, dacă adăugați o sarcină unui angajat și salvați informații despre angajat în baza de date, apoi sarcina va avea un nou executor în lista executorilor.