Tavolo di servizio
Ora diamo un'occhiata a un altro caso comune: molti a molti. Immaginiamo di avere una relazione molti-a-molti tra attività e dipendenti :
- Un dipendente nella tabella dei dipendenti può svolgere molte attività dalla tabella delle attività.
- Un'attività nella tabella delle attività può essere assegnata a più dipendenti.
Questa relazione tra entità è chiamata molti-a-molti. E per implementarlo a livello SQL, abbiamo bisogno di una tabella di servizio aggiuntiva. Chiamiamolo, ad esempio, employee_task.
La tabella employee_task conterrà solo due colonne:
- ID Dipendente
- task_id
Ogni volta che assegniamo un'attività specifica a un utente specifico, verrà aggiunta una nuova riga a questa tabella. Esempio:
ID Dipendente | task_id |
---|---|
1 | 1 |
1 | 2 |
2 | 3 |
Bene, la tabella delle attività dovrebbe perdere la sua colonna employee_id . Ha senso solo se l'attività può essere assegnata a un solo dipendente. Se l'attività può essere assegnata a più dipendenti, queste informazioni devono essere memorizzate nella tabella di servizio employee_task .
Relazione a livello di tabella
Ecco come saranno le nostre nuove tabelle:
id | nome | occupazione | stipendio | età | data di iscrizione |
---|---|---|---|---|---|
1 | Ivanov Ivan | Programmatore | 100000 | 25 | 2012-06-30 |
2 | Petrov Petr | Programmatore | 80000 | 23 | 2013-08-12 |
3 | Sergej Ivanov | Tester | 40000 | trenta | 2014-01-01 |
4 | Rabinovich Moisha | Direttore | 200000 | 35 | 2015-05-12 |
5 | Kirienko Anastasia | Capo ufficio | 40000 | 25 | 2015-10-10 |
6 | Vasca | Gatto | 1000 | 3 | 2018-11-11 |
Tabella dei dipendenti ( non modificata ) :
Questa tabella ha le seguenti colonne:
- id INT
- nome VARCHAR
- occupazione VARCHAR
- stipendio INT
- età INT
- join_date DATE
Ed ecco come appare la tabella delle attività , ha perso la colonna employee_id (contrassegnata in rosso):
id | impiegato_id | nome | scadenza |
---|---|---|---|
1 | 1 | Risolto un bug sul frontend | 2022-06-01 |
2 | 2 | Risolto un bug sul backend | 2022-06-15 |
3 | 5 | Compra il caffè | 2022-07-01 |
4 | 5 | Compra il caffè | 2022-08-01 |
5 | 5 | Compra il caffè | 2022-09-01 |
6 | (NULLO) | Pulisci l'ufficio | (NULLO) |
7 | 4 | Goditi la vita | (NULLO) |
8 | 6 | Goditi la vita | (NULLO) |
Questa tabella ora ha solo 3 colonne:
- id - numero di attività univoco (e righe nella tabella)
- employee_id - (rimosso)
- name - il nome e la descrizione dell'attività
- scadenza - il tempo fino al quale l'attività deve essere completata
Abbiamo anche la tabella di servizio employee_task , in cui i dati employee_id sono migrati dalla tabella delle attività:
ID Dipendente | task_id |
---|---|
1 | 1 |
2 | 2 |
5 | 3 |
5 | 4 |
5 | 5 |
(NULLO) | 6 |
4 | 7 |
6 | 8 |
Ho volutamente salvato temporaneamente la colonna eliminata nella tabella delle attività in modo che tu possa vedere che i dati da essa sono stati spostati nella tabella employee_task.
Un altro punto importante è la linea rossa "(NULL) 6" nella tabella employee_task. L'ho contrassegnato in rosso perché non sarà nella tabella employee_task .
Se l'attività 7 è assegnata all'utente 4, dovrebbe esserci una riga (4, 7) nella tabella employee_task.
Se l'attività 6 non è assegnata a nessuno, semplicemente non ci sarà alcun record per essa nella tabella employee_task. Ecco come appariranno le versioni finali di queste tabelle:
tabella delle attività :
id | nome | scadenza |
---|---|---|
1 | Risolto un bug sul frontend | 2022-06-01 |
2 | Risolto un bug sul backend | 2022-06-15 |
3 | Compra il caffè | 2022-07-01 |
4 | Compra il caffè | 2022-08-01 |
5 | Compra il caffè | 2022-09-01 |
6 | Pulisci l'ufficio | (NULLO) |
7 | Goditi la vita | (NULLO) |
8 | Goditi la vita | (NULLO) |
tabella employee_task:
ID Dipendente | task_id |
---|---|
1 | 1 |
2 | 2 |
5 | 3 |
5 | 4 |
5 | 5 |
4 | 7 |
6 | 8 |
Comunicazione a livello di classe Java
Ma con la comunicazione a livello di classi Entità, abbiamo un ordine completo. Cominciamo con la buona notizia.
Innanzitutto, Hibernate ha una speciale annotazione @ManyToMany che ti consente di descrivere bene il caso di una relazione di tabella molti-a-molti.
In secondo luogo, due classi Entity sono ancora sufficienti per noi. Non abbiamo bisogno di una classe per il tavolo di servizio.
Ecco come saranno le nostre classi. La classe Employee nella sua forma originale:
@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;
}
E la classe EmployeeTask nella sua forma originale:
@Entity
@Table(name="task")
class EmployeeTask {
@Column(name="id")
public Integer id;
@Column(name="name")
public String description;
@Column(name="deadline")
public Date deadline;
}
Annotazione @ManyToMany
Ometterò i campi esistenti negli esempi, ma ne aggiungerò di nuovi. Ecco come appariranno. Classe dipendente :
@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>();
}
E la classe 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>();
}
Sembra che tutto sia complicato, ma in realtà tutto è semplice.
Innanzitutto, utilizza l' annotazione @JoinTable (da non confondere con @JoinColumn), che descrive la tabella di servizio employee_task.
In secondo luogo, descrive che la colonna task_id della tabella employee_task si riferisce alla colonna id della tabella delle attività.
Terzo, dice che la colonna employee_id della tabella employee_task si riferisce alla colonna id della tabella employee.
Infatti, con l'aiuto delle annotazioni, abbiamo descritto quali dati sono contenuti nella tabella employee_task e come Hibernate dovrebbe interpretarli.
Ma ora possiamo facilmente aggiungere (ed eliminare) un'attività a qualsiasi dipendente. E aggiungi anche qualsiasi esecutore a qualsiasi attività.
Richiedi esempi
Scriviamo un paio di query interessanti per capire meglio come funzionano questi campi ManyToMany. E funzionano esattamente come previsto.
Innanzitutto, il nostro vecchio codice funzionerà senza modifiche, poiché il direttore aveva prima un campo delle attività:
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();
In secondo luogo, se vogliamo assegnare un altro esecutore a qualche compito, allora è ancora più semplice farlo:
Employee director = session.find(Employee.class, 4);
EmployeeTask task = session.find(EmployeeTask.class, 101);
task.employees.add(director);
session.update(task);
session.flush();
Importante! Come risultato dell'esecuzione di questa richiesta, non solo l'attività avrà un esecutore-direttore, ma anche il direttore avrà l'attività n. 101.
Innanzitutto, il fatto relativo alla relazione tra il direttore e l'attività nella tabella employee_task verrà memorizzato come stringa: (4,101).
In secondo luogo, i campi contrassegnati con le annotazioni @ManyToMany sono oggetti proxy e, quando vi si accede, viene sempre eseguita una query sul database.
Quindi, se aggiungi un'attività a un dipendente e salvi le informazioni sul dipendente nel database, successivamente l'attività avrà un nuovo esecutore nell'elenco degli esecutori.
GO TO FULL VERSION