CASE 4: (Unidirezionale @OneToMany con @JoinColumn) (Mappatura con associazione di chiave esterna)
Se usiamo @OneToMany
con @JoinColumn
allora ci saranno 2 tabelle. Come azienda, filiale
Nell’entità di cui sopra dentro @JoinColumn
, il nome si riferisce al nome della colonna chiave esterna che è companyId i.e company_id
e rferencedColumnName indica la chiave primaria cioè id
dell’entità (Company) a cui la chiave esterna companyId
si riferisce.
CASO 5: (Mappatura con associazione di chiave esterna)
Se usiamo @ManyToOne
con @JoinColumn
allora ci saranno 2 tabelle. Come azienda, filiale.
L’annotazione @JoinColumn
aiuta Hibernate a capire che c’è una colonna company_id
Foreign Key nella tabella filiale che definisce questa associazione.
Branch avrà la chiave straniera company_id
, quindi è il lato proprietario.
Ora, se persistiamo 1 azienda e 2 rami:
quando rimuoviamo la prima voce dalla collezione dei figli:
company.getBranches().remove(0);
Hibernate esegue due istruzioni invece di una :
- Prima rende nullo il campo chiave esterna (per rompere l’associazione con il genitore) poi cancella il record.
Update branch set branch_id = null where where id = 1 delete from branch where id = 1 ;
CASO 6: (Mappatura con join table)
Ora, consideriamo la relazione one-to-many
dove Person(id,name)
viene associato a più Veichle(id,name,number)
e più veicoli possono appartenere alla stessa persona.
Un giorno, mentre era sull’autostrada, lo sceriffo Carlos ha trovato alcuni veicoli abbandonati, molto probabilmente rubati. Ora lo sceriffo deve aggiornare i dettagli dei veicoli (numero, nome) nel loro database ma il problema principale è che non c’è un proprietario per quei veicoli, quindi il campo person_id(foreign key )
rimarrà nullo.
Ora lo sceriffo salva due veicoli rubati nel db come segue:
Vehicle vehicle1 = new Vehicle("ford", 1);
Vehicle vehicle2 = new Vehicle("gmc", 2);
List<Vehicle> vehicles = new ArrayList<>(); vehicles.add(vehicle1);
vehicles.add(vehicle2);
entityManager.persist(veichles);
Hibernate eseguirà le seguenti istruzioni SQL:
insert into vehicle (id, name, person_id) values (1, "ford", null); insert into vehicle (id, name, person_id) values (2, "gmc", null);id |name|person_id|
-----|----|---------|
1 |ford|NULL |
2 |benz|NULL |
---------------------
Questa strategia ci costringerà a mettere valori nulli nella colonna per gestire relazioni opzionali.
In genere, pensiamo alle relazioni many-to-many
quando consideriamo una join table
, ma, utilizzando una tabella di unione, in questo caso, possiamo eliminare questi valori nulli:
Questo approccio utilizza una tabella di unione per memorizzare le associazioni tra le entità Branch e Company. L’annotazione @JoinTable
è stata usata per fare questa associazione.
In questo esempio abbiamo applicato @JoinTable
sul lato Vehicle (Many Side).
Vediamo come sarà lo schema del database:
Il codice qui sopra genererà 3 tabelle come person(id,name)
, vehicle(id,name)
e vehicle_person(vehicle_id,person_id)
. Qui vehicle_person
terrà la relazione di chiave esterna per entrambe le entità Persona e Veicolo.
Così quando lo sceriffo salva i dettagli del veicolo, nessun valore nullo deve essere persistito nella tabella veicolo perché stiamo mantenendo l’associazione di chiave esterna nelle tabelle vehicle_person
non nella tabella veicolo.
Vehicle vehicle1 = new Vehicle("ford", 1);
Vehicle vehicle2 = new Vehicle("gmc", 2);
List<Vehicle> vehicles = new ArrayList<>(); vehicles.add(vehicle1);
vehicles.add(vehicle2);
entityManager.persist(veichles);
Hibernate eseguirà le seguenti istruzioni SQL:
insert into vehicle (id, name) values (1, "ford"); insert into vehicle (id, name) values (2, "gmc");id |name|
-----|----|
1 |ford|
2 |benz|
-----------
Questo esempio mostra come ci siamo sbarazzati dell’inserimento dei valori nulli.
@OneToMany Relazione bidirezionale
- L’associazione bidirezionale
@OneToMany
richiede anche un’associazione @ManyToOne
sul lato figlio. Anche se il modello di dominio espone due lati per navigare in questa associazione, dietro le quinte, il database relazionale ha solo una chiave esterna per questa relazione.
- Ogni associazione bidirezionale deve avere un solo lato proprietario (il lato figlio), l’altro è indicato come il lato inverso (o il
mappedBy
).
- Se usiamo il
@OneToMany
con l’attributo mappedBy
impostato, allora abbiamo un’associazione bidirezionale, il che significa che dobbiamo avere un’associazione @ManyToOne
sul lato figlio a cui il mappedBy
fa riferimento.
- L’elemento
mappedBy
definisce una relazione bidirezionale. Questo attributo permette di referenziare le entità associate da entrambi i lati.
Il modo migliore per mappare un’associazione @OneToMany
è quello di affidarsi al lato @ManyToOne
per propagare tutti i cambiamenti di stato delle entità.
Se persistiamo 2 Branch(s)
Hibernate genera una sola istruzione SQL per ogni entità Branch persistita:
insert into company (name, id) values ("company1",1);
insert into branch (company_id, name, id) values (1,"branch1",1); insert into branch (company_id, name, id) values (1,"branch2",2);
Se rimuoviamo un Branch:
Company company = entityManager.find( Company.class, 1L ); Branch branch = company.getBranches().get(0); company.removeBranches(branch);
C’è solo un’istruzione SQL di cancellazione che viene eseguita:
delete from Branch where id = 1
Quindi, l’associazione bidirezionale @OneToMany è il modo migliore per mappare una relazione di database uno-a-molti quando abbiamo davvero bisogno della collezione sul lato padre dell’associazione.
@JoinColumn
Specifica una colonna per unire un’associazione di entità o una collezione di elementi. L’annotazione @JoinColumn
indica che questa entità è il proprietario della relazione. Cioè la tabella corrispondente ha una colonna con una chiave esterna alla tabella di riferimento.
Nell’esempio precedente, il ramo Entità proprietario, ha una colonna Join chiamata company_id
che ha una chiave esterna all’entità Azienda non proprietaria.
Ogni volta che si forma un’associazione bidirezionale, lo sviluppatore dell’applicazione deve assicurarsi che entrambi i lati siano sempre in sincronia. addBranches()
e removeBranches()
sono metodi di utilità che sincronizzano entrambe le estremità ogni volta che un elemento figlio (cioè Branch) viene aggiunto o rimosso.
A differenza dell’unidirezionale @OneToMany
, l’associazione bidirezionale è molto più efficiente nella gestione dello stato di persistenza della collezione.
Ogni rimozione di elemento richiede solo un singolo aggiornamento (in cui la colonna della chiave esterna è impostata su NULL) e se il ciclo di vita dell’entità figlia è legato al suo genitore proprietario in modo che il figlio non possa esistere senza il suo genitore, allora possiamo annotare l’associazione con l’attributo orphan-removal e la dissociazione del figlio attiverà anche una dichiarazione di cancellazione sulla riga effettiva della tabella figlia.
NOTA:
- Nell’esempio bidirezionale di cui sopra il termine Genitore e Figlio in Entità/OO/Modello (cioè nella classe java) si riferisce rispettivamente al lato non vincente/inverso e al lato proprietario in SQL.
Nel punto di vista java la Società è il Genitore e il ramo è il figlio. Poiché un ramo non può esistere senza genitore.
Nel punto di vista SQL Branch è il lato proprietario e Company è il lato non proprietario (inverso). Dato che c’è 1 azienda per N rami, ogni ramo contiene una chiave esterna per l’azienda a cui appartiene. Questo significa che il ramo “possiede” (o letteralmente contiene) la connessione (informazione). Questo è esattamente l’opposto del mondo OO/modello.
@OneToMany Bi-directional Relationship(Mapping con join table)
Da CASE 6
abbiamo un mapping unidirezionale con @JoinTable
, quindi se aggiungiamo mappedBy
attributo all’entità Person allora la relazione diventerà bidirezionale.
SOMMARIO
Ci sono diverse cose da notare sulla suddetta mappatura:
- L’associazione
@ManyToOne
usa FetchType.LAZY
perché, altrimenti, ricadremmo nel fetching EAGER che non è buono per le prestazioni.
- Le associazioni bidirezionali dovrebbero essere sempre aggiornate su entrambi i lati, quindi il lato Parent dovrebbe contenere la combo
addChild
e removeChild
(Parent side Company contiene due metodi di utilità addBranches
e removeBranches
). Questi metodi ci assicurano di sincronizzare sempre entrambi i lati dell’associazione, per evitare problemi di corruzione di oggetti o dati relazionali.
- L’entità figlia, Branch, implementa i metodi equals e hashCode. Poiché non possiamo fare affidamento su un identificatore naturale per i controlli di uguaglianza, dobbiamo invece usare l’identificatore di entità. Tuttavia, dobbiamo farlo correttamente in modo che l’uguaglianza sia coerente in tutte le transizioni di stato delle entità. Poiché ci affidiamo all’uguaglianza per il removeBranches, è buona pratica sovrascrivere equals e hashCode per l’entità figlia in un’associazione bidirezionale.
- L’associazione @OneToMany è per definizione un’associazione genitore, anche se è unidirezionale o bidirezionale. Solo il lato genitore di un’associazione ha senso per far cascata delle sue transizioni di stato di entità ai figli.