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.