11 Hibernate3 Mapping delle associazioni

Hibernate
Mapping delle
associazioni
Dott. Doria Mauro
[email protected]
[email protected]
Mapping delle associazioni
Abbiamo visto come vi sia un mapping dedicato per le
associazioni tra classi entity e classi value.
Hibernate ha un mapping specifico per le associazioni tra classi
entity.
Gli oggetti entity, avendo un ciclo di vita proprio e potendo essere
condivisi, provedono un mapping più complesso.
Considereremo l’esempio:
Item
2
0..*
Bid
[email protected]
Mapping delle associazioni
NOTA:
A differenza degli EJB 2.1, Hibernate non prevede un gestore delle
associazioni bidirezionali.
Questo vuol dire che se si aggiunge un Bid ad un Item
aBid.setItem(anItem)
Hibernate NON aggiunge automaticamente l’oggetto Bid all’ insieme degli
item presente nella classe Item
anItem.getBids().add(aBid)
3
Hibernate considera l’associazione Tra Bid e Item e
l’associazione tra Item e Bid come due associazioni
separate (e così dovremmo fare anche noi).
[email protected]
Framework e container
Questo implica una fondamentale considerazione:
Il framework Hibernate non ha un container
4
Una gestione automatica delle associazioni richiederebbe la
presenza di un container sul framework Hibernate;
Hibernate non costruisce oggetti entity e quindi non ne controlla il
ciclo di vita; le operazioni sul framework invece avvengono
attraverso i metodi di persistenza.
Esattamente il contrario avviene su EJB 2.1 dove gli oggetti
entity sono gestiti da un container (CMP significa appunto
persistenza gestita dal container). Su EJB 2.1 le conseguenze di
questa caratteristica portano a situazioni molto criticate.
[email protected]
Associazioni in Hibernate
Le associazioni rappresentano il legame logico tra classi del modello e
possono essere:
–
–
–
5
Uno-a-uno
Uno-a-molti (che è equivalmente a molti-a-uno)
Molti-a-molti
Inoltre, è possibile rappresentare associazioni unidirezionali o
bidirezionali
L’unidirezionalità o la bidirezionalità è prima di tutto una scelta
progettuale.
NOTA:
bisogna tenere ben presente che nel modello relazionale del DB, esistono
soltanto relazioni unidirezionali.
[email protected]
Associazione tra class
Indipendentemente da Hibernate, le associazioni bidirezionali sono una
caratteristica del modello delle classi.
Item
List<Bid> l
get/setBids()
Bid
1..*
Item i
get/setItem()
NOTA: il rombo bianco significa
che un oggetto bid non è legato
indissolubilmente a un oggetto
item; la mancanza della freccia
denota la bidirezionalità della
associazione.
Nel nostro esempio, se si vuole aggiungere un Bid ad un item, si deve
necessariamente agire da entrambi i “lati” dell’associazione
unBid.addItem(unItem);
unItem.getBids().add(unBid);
6
è prima di tutto una questione di references: se si esegue soltanto la
seconda istruzione, la proprietà i (che è un puntatore) della classe Bid
rimarrebbe a null.
[email protected]
Associazione molti-a-uno
Come primo caso, consideriamo la classica associazione :
Item
1..*
Bid
Alla classe Item aggiungiamo una Collection di oggetti Bid
Se vogliamo una associazione bidirezionale, alla classe Bid
aggiungiamo una proprietà di tipo Item
NOTA: la bidirezionalità è interscambiabile tra le due classi
7
[email protected]
Associazione molti-a-uno
unidirezionale
La classe e il mapping per l’associazione molti-a-uno:
public class Bid {
<class name="Bid“ table="BID">
...
...
private Item item;
<many-to-one
public void setItem(Item item) {
name="item"
this.item = item;
column="ITEM_ID"
}
class="Item"
public Item getItem() {
not-null="true"/>
return item;
</class>
}
…..
...
}
Opzionale. Hibernate
Il campo ITEM_ID è
determina da solo il
posto not-null (abbiamo
Associazione unidirezionale
tipo della classe
una associazione uno-aassociata
molti e non zero-a-molti)
8
[email protected]
Associazione molti-a-uno
unidirezionale
Le tabelle sono:
Nel modello
relazionale, le relazioni
sono tutte bidirezionali.
9
E’ importante comprendere che l’associazione unidirezionale è
sufficiente. Potremmo scrivere sono associazioni unidirezionali
(tranne forse nel caso uno-a-uno).
Ad esempio, si possono scrivere querys per ottenere tutti i bid di
un item senza ricorrere ad una associazione bidirezionale.
[email protected]
Associazione molti-a-uno
bidirezionale
10
Uno dei motivi per cui si utilizza un ORM framework come
Hibenrate, è, però, quello di non dover scrivere querys ma agire
sugli oggetti. Aggiungiamo la bidirezionalità:
public class Item {
...
private Set bids = new HashSet();
public void setBids(Set bids) {
this.bids = bids;
}
public Set getBids() {
return bids;
}
public void addBid(Bid bid) {
bid.setItem(this);
bids.add(bid);
} ...}
<class name="Item“ table="ITEM">
...
Manca not-null
<set name="bids">
<key column="ITEM_ID"/>
<one-to-many class="Bid"/>
</set>
</class> E’ inutile indicare il nome della
tabella; è stata già indicata della
configurazione della classe Bid
NOTA: il tag <one-to-many> informa
Hibernate che la collection è composta
da references a oggetti entity e non da
oggetti value.
[email protected]
Associazione molti-a-uno
bidirezionale
Ricordandosi che il legame tra Item e Bid è rappresentato da:
–
–
–
Come fa Hibernate a gestire le associazioni considerato che
esiste un’unica colonna chiave esterna (ITEM_ID) a cui entrambe
le associazioni fanno riferimento?
Ricordarsi, inoltre, che Hibernate mantiene in memoria a run-time
due diverse rappresentazioni dell’associazione bidirezionale:
–
–
11
una relazione uno(item)-a-molti(bid) nel modello ad oggetti
una relazione molti(bid)-a-uno(item) nel modello ad oggetti
Una unica associazione bidirezionale tra item e bid del modello relazionale
Una collecion di bid nella classe Item
Una singolo item nella classe bid
[email protected]
Associazione molti-a-uno
bidirezionale
Questo implica, nel nostro esempio che se scrivo
bid.setItem(item);
bids.add(bid);
Le rappresentazioni in memoria delle associazioni vengono
modificate entrambe e anche sul db abbiamo 2 modifiche uguali
alla colonna ITEM_ID
Per avere una vera bidirezionalità è necessario aggiungere
l’attributo inverse. Tale attributo va aggiunto nella classe Item nel tag
<set>. Attenzione: la posizione dell’attributo inverse conta!
<set name="bids" inverse="true">
12
Hibenrate basa la sua logica di persistenza dalla posizione dell’attributo
inverse.
[email protected]
Associazione molti-a-uno
bidirezionale
Attenzione:
Avendo collocato l’attributo inverse nella classe Item, se si effettuano
modifiche attraverso la classe Item, Hibernate non le rende persistenti!
Quindi, ad esempio, aggiungere un nuovo bid alla collection in Item non
ha effetti sul DB!! Mentre, settando un Item su un oggetto bid, Hibernate
prendere in carico l’operazione rendendola persistente.
anItem.getBids().add(bid)
NO
DB
aBid.setItem(anItem)
SI
DB
In definitiva con inverse=“true”,
13
solo lo stato degli oggetti Bid è condierato da
Hibernate per la sincronizzazione sul DB!!!
[email protected]
Associazione molti-a-uno
bidirezionale
NOTA: E’ consigliabile scrivere dei metodi appositi che gestiscono in
maniera consistente le associazioni bidirezionali. Nel nostro
esempio, la classe Item ha il metodo:
public void addBid(Bid bid) {
bid.setItem(this);
bids.add(bid);
}
14
[email protected]
Associazione molti-a-uno
bidirezionale
Nell’ invocare il metodo addBid(), cosa abbiamo reso persistente?
Ad esempio, eseguendo il seguente codice, cosa è persistente?
Item newItem = new Item();
Bid newBid = new Bid();
newItem.addBid(newBid); //set di entrambi i lati dell’associazione
In questo caso, gli oggetti newItem e newBid sono appena
costruiti e Hibernate non sa ancora della propria esistenza
(Hibernate non ha un container!!). E’ necessario, quindi rendere
persistenti questi due oggetti:
session.save(newItem);
session.save(newBid);
15
A prima vista, la seconda istruzione sembrerebbe
superflua ma non è così! Gli oggetti Bid hanno un
ciclo di vita separato in quanto la classe Bid è di tipo
entity; quindi newBid va reso persistente!
[email protected]
Associazione molti-a-uno
transitive persistence: cascade=“save-update”
16
Si può procedere in tre possibili modi:
–
Gestione manuale: lo sviluppatore si prende carico della gestione
della persistenza di entrambi gli oggetti invocando i metodi save() o
delete() all’occorrenza oltre al normale codice di gestione della
associazione (come aggiungere o rimuovere bid dalla collezione).
–
Rivisitazione del modello dei dati: La classe Bid potrebbe essere
rivista come classe di tipo value. La persistenza degli oggetti Bid
sarebbe a carico di Hibernate ma si perderebbero le caratteristiche
degli entity (come la condivisione tra più oggetti)
–
Attivazione della gestione transitiva della persistenza: Hibernate
gestisce il ciclo di vita degli oggetti associati automaticamente
[email protected]
Associazione molti-a-uno
transitive persistence: cascade=“save-update”
17
Lo scopo è quello di mantenere un modello dei dati indipendente da
Hibernate ma nello stesso tempo effettuare soltanto le operazioni di
persistenza strettamente necessarie.
Hibernate possiede un meccanismo che attiva la persistenza a cascata
Nel nostro esempio, un nuovo oggetto Bid deve essere reso persistente
automaticamente insieme all’oggetto Item.
<class name="Item“ table="ITEM">
...
<set name="bids"
inverse="true"
cascade="save-update">
<key column="ITEM_ID"/>
<one-to-many class="Bid"/>
</set>
</class>
Save-update attiva la persistenza
transitiva su oggetti Bid associati
ad oggetti Item (nella collection).
NOTA: l’opzione cascade può essere
applicata anche nella classe Bid. In
questo caso però, un Bid viene
costruito sempre dopo un Item e
quindi non avrebbe senso.
[email protected]
Associazione molti-a-uno
transitive persistence: cascade=“save-update, delete”
Cosa fare in caso di cancellazione di oggetti Bid? Al momento, se si
vogliono eliminare tutti i bid di un item, bisogna procedere manualmente:
Item anItem = // qui il codice di caricamento di un item
// cancellazione manuale di tutte le insanze bid legate all’oggetto item
for ( Iterator<Bid> it = anItem.getBids().iterator(); it.hasNext(); ) {
Bid bid = it.next();
it.remove(); // Rimozione dei bid dalla collection
session.delete(bid); // Eliminazione dei bid dal DB
}
session.delete(anItem); // Infine, rimozione dell’ item dal DB
18
E’ possibile,
cancellazione.
però,
rendere
<set name="bids"
inverse="true"
cascade="save-update, delete">
transitiva
anche
l’operazione
di
NOTA: la cancellazione a cascata ha
senso nelle composizioni (rombo
pieno nel diagramma UML.
[email protected]
Associazione molti-a-uno
transitive persistence: cascade=“save-update, delete”
Gli oggetti entity possono essere condivisi tra più elementi nel
modello ad oggetti. Cosa succedere se gli oggetti Bid sono
associati anche con, ad esempio, un oggetto User?
Item
1..*
Bid
1..*
User
Chiave esterna
verso ITEM
Chiave esterna
verso USER
19
[email protected]
Associazione molti-a-uno
transitive persistence: cascade=“save-update, delete”
20
Tentando di eliminare tutti i bid di un item con l’opzione cascade attiva,
non si produrrebbero exception in quanto sul DB non si avrebbe
nessuna violazione di vincoli.
Però, a livello Java, un User avrebbe una collection di references a
oggetti Bid che non esistono più sul DB
E’ necessario rimuovere tutti i references agli oggetti bid prima di
cancellare un item e, quindi, a cascata tutti i bids. E’ quindi necessario
rimuovere tutti gli oggetti bid dalla collection in User.
Bisogna quindi “seguire” i puntatori e gestire correttamente i legami tra
gli oggetti.
NOTA: una cancellazione a cascata che viola un vincolo sul DB genera
una exception
[email protected]
Associazione molti-a-uno
transitive persistence: cascade="save-update, delete, delete-orphan”
Esiste una ulteriore opzione per la cancellazione di oggetti entity.
21
L’opzione delete-orphan fa si che Hibernate cancelli oggetti entity
dalla collection soltanto se non vi sono ulteriori puntatori
associati ad esso.
Nel nostro esempio, cancellando un elemento dalla collection
degli oggetti bid, questo si cancellerà soltanto se non vi sono altri
oggetti che hanno un reference verso di esso.
<set name="bids"
inverse="true"
cascade="save-update, delete, delete-orphan">
[email protected]
Associazione uno-a-uno
22
Esaminiamo adesso una associazione uno-a-uno tra due classi
entity
La classe Address, in questo caso è un entity in quanto un
oggetto address può essere condiviso tra un oggetto User e uno
Shipment
[email protected]
Associazione uno-a-uno
La classe Address avrà la seguente configurazione:
<class name="Address" table="ADDRESS">
<id name="id" column="ADDRESS_ID">
<generator class="foreign">
<param name="property">user</param>
</generator>
</id>
<property name="street" column="STREET"/>
<property name="city" column="CITY"/>
<property name="zipcode" column="ZIPCODE"/>
</class>
23
La classe Address ora ha
un identificatore.
L’identificatore è di tipo
speciale dedicato alle
associazioni uno-a-uno.
Ora,
Address
è
subordinato a User: un
Address esiste solo se
esiste una User associato.
Una relazione uno-a-uno significa che l’identificatore di Address
fa da chiave esterna con l’identificatore di User
La classe User avrà un normale identificatore
[email protected]
Associazione uno-a-uno
La classe User e la sua configurazione:
public class User {
...
private Address shippingAddress;
// Getters and setters
}
24
<one-to-one name="shippingAddress"
class="Address"
cascade="save-update"/>
La classe Address e la sua configurazione:
public class Address {
...
private User user;
// Getters and setters
}
persistenza automatica di
Address quando si fa una
operazione di persistenza
su User
Vincolo di link tra le due
chiavi primarie
<one-to-one name="user"
class="User"
constrained="true"/>
Questo tipo di mapping non determina soltanto una associazione
bidirezionale uno-a-uno ma grazie all’attributo constrained determina la
migliore associazione con la classe User.
[email protected]
Associazione uno-a-uno
25
Le tabelle sono:
Quando si rende persistente un nuovo oggetto Address, questo è
sempre associato ad un oggetto User.
Hibernate prende la chiave primaria dell’oggetto User associato, e la
usa come valore per la chiave primaria del nuovo oggetto Address
quando lo inserisce nella tabella.
[email protected]
Associazione uno-a-uno
User newUser = new User();
Address shippingAddress = new Address();
newUser.setShippingAddress(shippingAddress);
session.save(newUser);
NO
User newUser = new User();
Address shippingAddress = new Address();
shippingAddress.setUser(newUser);
newUser.setShippingAddress(shippingAddress);
session.save(newUser);
SI
26
A questo punto possiamo procedere con la creazione di un User
con il suo Address.
Conviene quindi modificare
uno dei metodi set:
NOTA: questo produce un
errore! Manca il legame tra
l’oggetto shippingAddress e
l’user!
Questo
è
un
problema logico di OO non
derivato da Hibernate!
Oppure si modifica il
metodo setUser nella
classe Address
public void setAddress(Address address) {
address.setUser(this);
this.address = address;
}
[email protected]
Associazione uno-a-uno
27
E’ possibile realizzare diversamente una associazione uno-a-uno
tra tabelle. Si può aggiungere una chiave esterna in una tabella
verso la chiave primaria dell’altra.
Il modello relazionale indica come questa soluzione non sia altro
che una relazione uno-a-molti con la colonna chiave esterna
vincolata a non avere duplicati (vincolo unique). Hibernate segue
esattamente queste indicazioni.
Infatti, nonostante si tratti di una associazione uno-a-uno,
Hibernate vuole un tag <many-to-one> se si intende seguire
questa strategia.
[email protected]
Associazione uno-a-uno
Modifichiamo la configurazione della classe User:
<class name="User" table="USERS">
….
<many-to-one name="shippingAddress"
class="Address"
column="SHIPPING_ADDRESS_ID"
cascade="save-update"
unique="true"/>
</class>
A questo punto va aggiunto un riferimento anche in Address:
<one-to-one name="user“ class="User"
property-ref="shippingAddress"/>
28
NOTA: adottando questa
soluzione, è possibile far
condividere un oggetto
address con diversi tipi
di
oggetti:
basta
aggiungere più chiavi
esterne.
<property-ref>
indica
ad
Hibernate che la proprietà
user della classe Address
rappresenta
l’altro
lato
dell’associazione.
[email protected]
Associazione uno-a-uno
29
Le tabelle sono queste:
[email protected]
Associazione uno-a-uno opzionale
30
Consideriamo l’associazione tra Item e Shipment. Questa è una
associazione uno-a-uno opzionale, cioè un item potrebbe essere
in associazione con un Shipment ma non è obbligatorio.
Il modello relazionale ci suggerisce una soluzione basata su una
tabella intermedia.
[email protected]
Associazione uno-a-uno opzionale
31
Le tabelle sono queste:
Quando un Item è in associazione con Shipment, si produce una nuova
riga nella tabella intermedia.
Il vantaggio di questa soluzione è che l’associazione è rappresentata ad
di fuori delle tabelle ITEM e SHIPMENT e che, inoltre, non è necessario
aggiungere chiavi esterne con valori nulli.
[email protected]
Associazione uno-a-uno opzionale
Hibernate possiede una soluzione elegante per rappresentare in
maniera trasparente la tabella intermedia.
Essendoci delle chiavi esterne, queste andrebbero configurate con il tag
<many-to-one> ma nella tabella intermedia. Hibernate possiede un tag
apposito per mappare le tabelle intermedie: <join>.
<class name="Shipment" table="SHIPMENT">
Nome tabella intermedia
<id name="id" column="SHIPMENT_ID">...</id>
...
32
<join table="ITEM_SHIPMENT" optional="true">
<key column="SHIPMENT_ID"/>
<many-to-one name="auction"
column="ITEM_ID"
not-null="true"
unique="true"/>
</join>
</class>
chiave primaria della tabella
ITEM_SHIPMENT (che fa
anche da chiave esterna
verso SHIPMENT
Chiave esterna verso ITEM
rende la relazione uno-a-uno
[email protected]
Associazione uno-a-molti
33
Per le associazioni uno-a-molti, la migliore collection che si può
scegliere è bag.
Hibernate, non dovendo mantenere l’ordine di inserimento o
controllare se vi sono valori duplicati, aggiunge elementi alla
collezioni senza doverla caricare in memoria.
Questo è fondamentale se si intende manipolare grandi insiemi
di dati.
[email protected]
Associazione uno-a-molti
Se in una collection si deve mantenere la posizione degli
elementi, la scelta migliore è List ma tali posizioni si possono
aggiungere in una colonna che fa da indice.
<class name="Item“ table="ITEM">
...
<list name="bids">
<key column="ITEM_ID“not-null="true"/>
<list-index column="BID_POSITION"/>
<one-to-many class="Bid"/>
</list>
</class>
Qui riportiamo l’esempio di
associazione
uno-a-molti
unidirezionale tra Item e Bid
34
Deve essere not-null se
l’associazione è unidirezionale
[email protected]
Associazione uno-a-molti
35
Se, però, consideriamo l’associazione bidirezionale tra Item e Bid,
L’aggiunta di una colonna indice provoca problemi per la gestione
unilaterale della persistenza (dovuta all’attributo inverse=“true”).
L’attributo inverse si può aggiungere solo nella classe che contiene la
collezione e, quando è true, significa che Hibernate ignora le operazioni
fatte su di essa.
Nel nostro esempio, Hibernate ignora le operazioni fatte sull’oggetto
item alla sua (interna) lista dei bids; solo lo stato degli oggetti Bid è
condierato da Hibernate per la sincronizzazione sul DB.
Ma come fa Hibernate a gestire l’indice se ignora la collezione dei Bids?
E’ necessario invertire la logica dell’inverse!!
[email protected]
Associazione uno-a-molti
Non è possibile aggiungere l’attributo inverse nel mapping della
classe Bid ma si può ottenere lo stesso effetto aggiungendo gli
attributi insert e update posti a false.
<class name="Bid“ table="BID">
...
<many-to-one name="item“
column="ITEM_ID"
class="Item"
not-null="true"
insert="false"
update="false"/>
</class>
<class name="Item" table="ITEM">
...
<list name="bids" inverse="false">
<key column="ITEM_ID“ not-null="true"/>
<list-index column="BID_POSITION"/>
<one-to-many class="Bid"/>
</list>
</class>
Insert e update messi a false significa
che gli oggetti Bid sono read-only
36
Di default è false, quindi si
può omettere
NOTA: tale inversione è necessaria per tutte le collezioni che mantengono la
posizione degli elementi (quindi indicizzabili): List, Map e array.
[email protected]
Associazione molti-a-molti
Nei progetti reali non vi sono molte associazioni molti-a-molti.
Normalmente, è possibile rappresentare tale associazione con
due associazioni molti-a-uno.
Consideriamo il seguente esempio:
Category
37
1..* presenta > 1..*
Item
Il modello relazionale prevede una tabella intermedia per questo
tipo di associazione. Hibernate segue questa logica, prevedendo
un mapping con una tabella intermedia.
[email protected]
Associazione molti-a-molti
38
Le tabelle sono:
La classe intermedia ha una chiave esterna per ognuna delle
due tabelle in relazione; queste, insieme fanno chiave primaria.
Eventualmente possono essere presenti altre colonne
[email protected]
Associazione molti-a-molti
unidirezionale
Per realizzare una associazione unidirezionale, basta aggiungere
una collezione in una delle due classi.
Ad esempio, aggiungiamo nella classe Category una collezione
di oggetti item :
<set name="items“ table="CATEGORY_ITEM"
cascade="save-update">
<key column="CATEGORY_ID"/>
<many-to-many class="Item“
column="ITEM_ID"/>
</set>
Chiave esterna nella tabella
intermedia
39
public class Category {
private Set<Item> items = new HashSet<Item>();
…
public void addItem(Item item) {
this.items.add(item);
}
//metodi get/set
Il tag <many-to-many> indica la presenza di una tabella
intermedia verso Item
[email protected]
Associazione molti-a-molti
unidirezionale
E’ possibile utilizzare una collection Bag se si prevede la
presenza di duplicati o una lista eventualmente indicizzata:
<idbag name="items“ table="CATEGORY_ITEM”
cascade="save-update">
<collection-id type="long“column="CATEGORY_ITEM_ID">
<generator class=“increment"/>
<key column="CATEGORY_ID"/>
</collection-id>
<many-to-many class="Item" column="ITEM_ID"/>
</idbag>
40
<list name="items“ table="CATEGORY_ITEM"
cascade="save-update">
<key column="CATEGORY_ID"/>
<list-index column="DISPLAY_POSITION"/>
<many-to-many class="Item" column="ITEM_ID"/>
</list>
[email protected]
Associazione molti-a-molti
bidirezionale
Proviamo a realizzare la stessa associazione ma bidirezionale
<class name="Category" table="CATEGORY">
...
<set name="items“ table="CATEGORY_ITEM"
cascade="save-update">
<key column="CATEGORY_ID"/>
<many-to-many class="Item" column="ITEM_ID"/>
</set>
NOTA: l’attributo inverse
può essere posizionato
a scelta in una delle due
collection.
41
L’attributo cascade è inutile
per le associazioni molti-amolti
<class name="Item" table="ITEM">
...
<set name="categories“ table="CATEGORY_ITEM”
inverse="true"
cascade="save-update">
<key column="ITEM_ID"/>
<many-to-many class="Category“ column="CATEGORY_ID"/>
</set>
</class>
[email protected]
Associazione molti-a-molti
bidirezionale
E’ possibile scegliere un tipo di collection diverso per le due
classi.
NOTA:
Non è possibile utilizzare collections indicizzate dal lato inverse della
associazione, il che significa anche che non possono essere entrambe
indicizzate
42
[email protected]
Associazione molti-a-molti
aggiungere colonne alla tabella intermedia
Il modello relazionale prevede l’ aggiunta di colonne alla tabella
intermedia della associazione molti-a-molti per rappresentare
informazioni inerenti al legame che intercorre tra due record.
Hibernate consente a sua volta l’aggiunta di colonne nel mapping
della associazione.
Aggiungiamo ad esempio,
due colonne alla tabella
intermedia: nome dell’user e
data dell’operazione
43
[email protected]
Associazione molti-a-molti
aggiungere colonne alla tabella intermedia
Esistono due soluzioni diverse al problema. La prima e più
semplice prevede la creazione di una nuova classe di tipo value
(nel nostro esempio potremmo chiamarla CategorizedItem):
public class CategorizedItem {
private String username;
private Date dateAdded = new Date();
private Item item;
private Category category;
44
public CategorizedItem(String name, Category category,
Item item) {
this.category = category;
this.item = item;
}
...
// metodi get/set
}
Essendo la classe di tipo
value, non c’è un campo
identificatore.
Sempre in quanto classe
value, deve essere
subordinato ad una classe
entity: scegliamo Category
[email protected]
Associazione molti-a-molti
aggiungere colonne alla tabella intermedia
45
La classe Category va quindi mappata con una collezione di
oggetti value di tipo CategorizedItem.
<class name="Category" table="CATEGORY">
La
chiave
primaria
è
...
composta da tutte le colonne
<set name="categorizedItems" table="CATEGORY_ITEM">
della tabella intermedia
<key column="CATEGORY_ID"/>
<composite-element class="CategorizedItem">
Tutte le colonne devono
<parent name="category"/>
essere not-null (vedi mapping
<many-to-one name="item"
column="ITEM_ID"
delle collection di oggetti
not-null="true"
value)
class="Item"/>
<property name="username" column="ADDED_BY_USER"/>
<property name="dateAdded" column="ADDED_ON"/>
</composite-element>
</set>
Le due colonne aggiuntive
</class>
[email protected]
Associazione molti-a-molti
aggiungere colonne alla tabella intermedia: associazione ternaria
Approfittiamo dell’esempio per mostrare il mapping di una
associazione ternaria:
<set name="categorizedItems“ table="CATEGORY_ITEM">
<key column="CATEGORY_ID"/>
<composite-element class="CategorizedItem">
<parent name="category"/>
<many-to-one name="item"
column="ITEM_ID"
not-null="true"
class="Item"/>
<many-to-one name="user"
column="USER_ID"
not-null="true"
class="User"/>
<property name="dateAdded" column="ADDED_ON"/>
</composite-element>
</set>
46
L’user lo rappresentiamo
come associazione con la
classe User e non solo con il
nome
[email protected]
Associazione molti-a-molti
aggiungere colonne alla tabella intermedia
Per aggiungere elementi in associazione tra Item e Category,
agiremo attraverso gli oggetti category.
Qui consideriamo la colonna aggiuntiva userName
CategorizedItem unLink = new CategorizedItem(unUser.getUserName(), unaCategory, unItem);
aCategory.getCategorizedItems().add( aLink );
aCategory.getCategorizedItems().remove( aLink );
47
La classe CategorizedItem è subordinata alla classe Category,
per cui la navigazione verso gli oggetti item non è possibile (gli
oggetti value non sono raggiungibili da altri tipi di oggetti)
[email protected]
Associazione molti-a-molti
unidirezionale utilizzando collection map
Proviamo ad utilizzare collection di tipo Map per le associazioni molti-amolti.
Partiamo dal presupposto che le chiavi delle mappe sono oggetti di tipo
value mentre i valori sono di tipo entity
Consideriamo il seguente esempio dove la classe Item possiede una
collection Map le cui chiavi sono gli identificatori dei bid e i valori, i
references verso gli oggetti bid.
Item
48
Map
1..*
(1, puntatore all’oggetto bid 1)
(2, puntatore all’oggetto bid 2)
….
1..*
Bid
NOTA: la composizione
delle tabelle sul DB
non cambia rispetto
agli esempi precedenti
[email protected]
Associazione molti-a-molti
unidirezionale utilizzando collection map
La configurazione della mappa per la classe Item è:
<map name="bidsByIdentifier">
<key column="ITEM_ID"/>
<map-key type="long" formula="BID_ID"/>
<one-to-many class="Bid"/>
</map>
Cosa succede se la chiave della mappa è un oggetto entity? Si
ottiene il mapping di una associazione ternaria.
Item
49
Map
La presenza della formula
rende la colonna ITEM_ID
read-only
1..*
1..*
Bid
(puntatore ad un oggetto User x, puntatore all’oggetto bid a)
(puntatore ad un oggetto User y, puntatore all’oggetto bid b)
….
Nel nostro esempio,
potremmo avere come
chiave della mappa un
oggetto
User
(si
intende un puntatore
ad un oggetto User).
[email protected]
Associazione molti-a-molti
bidirezionale utilizzando collection map
50
Le tabelle sono queste:
[email protected]
Associazione molti-a-molti
unidirezionale utilizzando collection map
Nel mapping si introduce un nuovo tag appositamente pensato
per questi casi:
<map name="itemsAndUser“ table="CATEGORY_ITEM">
<key column="CATEGORY_ID"/>
<map-key-many-to-many column="ITEM_ID" class="Item"/>
<many-to-many column="ADDED_BY_USER_ID" class="User"/>
</map>
Il tag <map-key-many-to-many> crea un link tra le tre classi. Per
aggiungere un elemento alla mappa:
aCategory.getItemsAndUser().add( anItem, aUser );
51
Infine, si potrebbe rendere l’associazione bidirezionale
aggiungendo una collection di oggetti category nella classe Item
[email protected]
Domande?
join
constrained
one-to-one
inverse
One-to-many
cascade
not-null
bidirezionale
52
many-to-many