In questo articolo si completa l'operazione di mapping del modello in esame e ne vengono messi in evidenza gli aspetti legati alle relazioni tra le classi.
Introduzione
Nella prima parte dell'articolo [1] si è visto come mappare una classe con semplici proprietà e si sono viste le basilari operazioni che possono essere fatte con il framework per reperire, salvare, modificare e cancellare gli oggetti persistiti. In questa seconda parte si completerà l'operazione di “mapping”, mettendo in evidenza gli aspetti legati alla gestione delle relazioni intercorrenti tra le classi
...dopo il Frutto arriva il Verme
##
Nella figura 1 viene riportato il modello su cui stiamo lavorando, in rosso sono evidenziate le classi che sono state prese in considerazione nella prima parte dell'articolo [], ora, si persisteranno le classi rimanenti evidenziando la gestione delle relazioni. Nel modello un frutto può avere nessuno o più vermi (0,*) mentre un verme (se è fortunato) si sta mangiando al massimo un frutto la relazione quindi è di tipo uno a molti da Frutto verso Vermi. Una relazione in senso generale, per ciò che concerne il nostro problema, ha una natura duale: la sua rappresentazione in termini di linguaggio Java e la sua rappresentazione in termini di database relazionale. La relazione Verme-Frutto in linguaggio Java viene quindi realizzata con una proprietà della classe Frutto nella classe Verme e un proprietà di tipo java.util.Collection (di vermi) nella classe Frutto. La medesima relazione viene, in termini relazionali, rappresentata con una chiave esterna nella tabella “vermi” (vedi listato 2) recante l'id del Frutto associato al Verme.
Listato 1 - Il file di mapping della classe Verme (Verme.cfg.xml) <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="lc.hibernate.Verme" table="vermi" > <id name="id" type="int" column="v_id" unsaved-value="0"> <generator class="identity"/> </id>
<property name="nome"/> <many-to-one name="frutto" column="f_id" class="lc.hibernate.Frutto" cascade="none"/>
</class> </hibernate-mapping>
Listato 2 - Codice SQL di definizione della tabella “vermi” CREATE TABLE vermi ( vid int(11) unsigned NOT NULL default '0', nome varchar(20) default NULL, fid int(11) unsigned default NULL, PRIMARY KEY (vid), KEY fid (f_id) ) TYPE=MyISAM;
In tutto il listato 1 vediamo come una novità è rappresentata dal costrutto many-to-one, tramite esso indichiamo al framework che la proprieta specificata dall'attributo name è della classe lc.hibernate.Frutto inoltre, tramite l'attributo column indichiamo qual'è la colonna della tabella vermi che contiene la chiave primaria del Frutto a cui è associato il Verme. Un attributo interessante dell'elemento many-to-one è cascade esso indica ad Hibernate che tipo di operazioni devono essere propagate all'elemento associato. Questo attributo è presente ovunque vi sia una relazione tra classi del modello, i possibili valori che può assumere sono: none: nessuna operazione viene propagata all'oggetto associato save-update: le operazioni di salvataggio e aggiornamento si propagano all'oggetto associato per esempio il codice del listato 3 comporterebbe il salvataggio di un nuovo Verme e automaticamente Hibernate invocherebbe il salvataggio del frutto associato. Occorre fare molta attenzione con questo attributo: se si fosse associato al Verme una mela già presente nel database oppure, con riferimento al listato 3 dopo aver creato la Mela avessi invocato mela.setId(2); con “2” chiave di una mela già presente nel db, l'operazione avrebbe dato errore perché avrei avuto un duplicato della chiave primaria. In questo caso hibernate non si cura di vedere se l'oggetto associato ha o non ha un id settato e di conseguenza non è in grado di “decidere” se eseguire una INSERT o una UPDATE: il framework esegue ciecamente la stessa operazione che è stata chiamato sull'oggetto referente (nel nostro caso save). Delete: propaga all'oggetto associato solo le operazioni di cancellazione all: vengono propagate all'oggetto tutte le operazioni: save, update e delete
Listato 3 - Operazione di salvataggio a cascata sess = sf.openSession(c);
Mela mela= new Mela();
mela.setColore("giallo_verme");
mela.setMarcio(false);
mela.setTipologiaMela("golden_verme");
Verme v=new Verme();
v.setNome("Malvasio");
v.setFrutto(mela);
sess.save(v);
Listato 4 - Sequenza di operazioni SQL eseguite in trasparenza da Hibernate relative al listato 3 Hibernate: insert into frutta (tipologiamela, colore, ismarcio, tipo) values (?, ?, ?, 'mela') Hibernate: select lastinsertid() Hibernate: insert into vermi (nome, fid) values (?, ?) Hibernate: select lastinsert_id()
Nel listato 4 si nota come vengono “tradotte” le operazioni svolte nel listato 3. Questo output lo si ottiene consultando i files di log sopo aver impostato sul file di configurazione la proprieta vista in [] <property name="show_sql">true</property>. Dal punto di vista concettuale sarebbe stato corretto creare prima il verme, prelevare la mela da associare, associarla al verme e salvare il verme lasciando cascade=”none” (listato 6).
Hibernate è in grado di gestire la persistenza di tutti gli oggetti appartenenti al Java Collection Framework [], per far ciò nel file di configurazione possiamo far uso di uno dei seguenti elementi <set>, <list>, <map>, <bag>, <array> e <primitive-array>. List, Array vengono usati qualora si voglia gestire una collezione di oggetti ordinati ed indicizzati, in tal caso sarà necessario aggiungere alla tabella degli oggetti persistiti una colonna contenete l'indice posizionale dell'oggetto all'interno della collezione. Hash rappresenta una collezione i cui oggetti sono accessibili tramite una chiave alfa-numerica (concetto generale di array associativo) e per questo motivo, come nel caso precedente, la tabella relazionale dovrà contenere una colonna aggiuntiva recante la chiave a cui verrà associato l'oggetto. Infine, Set e Bag sono due collezioni costituite da oggetti non indicizzati, sono semplicemente dei contenitori, la differenza tra le due è di carattere squisitamente semantico: Bag può contenere lo stesso oggetto più volte, Set invece no. In questo articolo le collezioni sono rappresentate dall'elemento Bag (il più flessibile) ma i discorsi che faremo valgono per tutti gli altri tipi di collezione.
Listato 5 - File di mapping relativo alla classe Frutto (Frutto.hbm.xml) <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="lc.hibernate.Frutto" table="frutta" discriminator-value="not null" > <id name="id" type="int" column="f_id" unsaved-value="0"> <generator class="identity"/> </id> <discriminator column="tipo" type="string"/> <property name="colore"/> <property name="marcio" column="is_marcio"/> <bag name="vermi" cascade="none"> <key column="f_id"/> <one-to-many class="lc.hibernate.Verme"/> </bag>
<subclass name="lc.hibernate.Mela" discriminator-value="mela"> <property name="tipologiaMela" column="tipologia_mela"/> </subclass> <subclass name="lc.hibernate.Banana" discriminator-value="banana"> <property name="coloreBollino" column="colore_bollino"/> </subclass>
</class> </hibernate-mapping>
nel listato 5 si (ri)vede “la mappatura” di Frutto questa volta, a differenza di quanto visto in [], notiamo la comparsa dell'elemento bag che rappresenta l'altro lato della relazione Verme-Frutto. Gli attributi di bag non costituiscono nulla di nuovo, name indica il nome della proprietà di Frutto che contiene la collezione di Vermi, nel nostro caso: “private Collection vermi” con relativi metodi di accesso get e set pubblici. I sotto elementi di bag sono: <key column="f_id"/> indica la chiave esterna dalla tabella di collezione verso la tabella della classe proprietaria. Il nome della colonna della tabella vermi che contiene la chiave primarei della mela a cui il verme è associato <one-to-many class="lc.hibernate.Verme"/> indica semplicemente che la collezionone è l'altra parte di una relazione molti a uno e che tale collezione deve essere riempita da oggetti della classe Verme
Eseguiamo ora il codice del listato 6 che comporta l'associazione di due nuovi vermi con la mela con id 11. Listato 6 - Associazione di due vermi con una mela già presente sess = sf.openSession(c);
Frutto f=(Frutto) sess.load(Frutto.class,new Integer(11));
Verme v=new Verme();
v.setNome("Ettore");
v.setFrutto(f);
sess.save(v);
v=new Verme();
v.setNome("Matilde");
v.setFrutto(f);
sess.save(v);
Ora recuperando in un'altra sessione il frutto con id 11 tramite la seconda istruzione del listato 6, Hibernate eseguirà automaticamente le seguenti due istruzioni:
Hibernate: select frutto0.fid as fid0, frutto0.tipo as tipo0, frutto0.colore as colore0, frutto0.ismarcio as ismarcio0, frutto0.tipologiamela as tipologi50, frutto0.colorebollino as coloreb60 from frutta frutto0 where frutto0.fid=?
Hibernate: select vermi0.fid as f_id__, vermi0.vid as v_id__, vermi0.vid as vid0, vermi0.nome as nome0, vermi0.fid as fid0 from vermi vermi0 where vermi0.f_id=?
La prima recupera il frutto e la seconda recupera i vermi associati al frutto, ci ritroviamo l'oggetto frutto con la collezione istanziata e costituita dai due vermi “Ettore” e “Matilde” (listato 6).
Il Fruttivendolo: molti a uno monodirezionale
Il listato 7 mostra il file di mapping per la classe Fruttivendolo.
Listato 7- File di mapping relativo alla classe Fruttivendolo (Fruttivendolo.hbm.xml) <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <!-- lc.hibernate.Fruttivendolo root --> <class name="lc.hibernate.Fruttivendolo" table="fruttivendoli"> <id name="id" type="int" column="frid" unsaved-value="0"> <generator class="identity"/> </id> <bag name="frutta" table="frutta" cascade="all"> <key column="frid"/> <one-to-many class="lc.hibernate.Frutto" /> </bag> <property name="ragioneSociale" column="ragione_sociale" type="string"/> <bag name="contadini" table="fruttivendoli_contadini" cascade="none"> <key column="fr_id"/> <many-to-many class="lc.hibernate.Contadino" column="c_id"/> </bag> </class> </hibernate-mapping>
Lasciando per ora perdere l'elemento <bag name="contadini"... che vedremo in seguito, non notiamo nulla di nuovo rispetto a quanto descritto fino ad ora: abbiamo l'id, la proprietà ragioneSociale semplice e una collezione uno a molti identica a quella presente da Frutto verso Verme. La differenza tra le due relazioni (Verme-Frutto e Mela-Fruttivendolo) sta nel fatto che in Frutto non esiste alcun riferimanto a fruttivendolo, non vi è alcun modo di controllare Fruttivendolo a partire da un Frutto, cosa che invece era possibile con Verme. La relazione in esame è interessante perché mette alla luce la possibilità di controllare la persistenza della frutta attraverso i riferimenti contenuti nella collezione di Fruttivendolo
Listato 8 - Creiamo un fruttivendolo con un frutto sess = sf.openSession(c);
Mela mela= new Mela();
mela.setColore("Blue");
mela.setMarcio(false);
mela.setTipologiaMela("PincoPalla");
Fruttivendolo fr=new Fruttivendolo();
fr.setRagioneSociale("Raul e Co");
Vector frutta=new Vector();
sess.save(fr);
sess.save(mela);
frutta.add(mela);
fr.setFrutta(frutta);
sess.flush();
Listato 9- Codice SQL relativo all'esecuzione del listato 8 Hibernate: insert into fruttivendoli (ragionesociale) values (?) Hibernate: select lastinsertid() Hibernate: insert into frutta (tipologiamela, colore, ismarcio, tipo) values (?, ?, ?, 'mela') Hibernate: select lastinsertid() Hibernate: update frutta set frid=? where fid=?
Listato 10 - Aggiungiamo un frutto al fruttivendolo
sess = sf.openSession(c);
Fruttivendolo f=(Fruttivendolo) sess.load(Fruttivendolo.class,new Integer(4));
Frutto fr= (Frutto) sess.load(Frutto.class,new Integer(1));
f.getFrutta().add(fr);
sess.flush();
Listato 11- Codice SQL relativo all'esecuzione del listato 10 Hibernate: select fruttivend0.frid as frid0, fruttivend0.ragionesociale as ragione20 from fruttivendoli fruttivend0 where fruttivend0.frid=? Hibernate: select contadini0.frid as frid__, contadini0.cid as c_id__, contadino1.id as id0, contadino1.ragionesociale as ragione20 from fruttivendolicontadini contadini0 inner join contadini contadino1 on contadini0.cid=contadino1.id where contadini0.frid=? Hibernate: select frutta0.frid as frid__, frutta0.fid as f_id__, frutta0.fid as fid0, frutta0.tipo as tipo0, frutta0.colore as colore0, frutta0.ismarcio as ismarcio0, frutta0.tipologiamela as tipologi50, frutta0.colorebollino as coloreb60 from frutta frutta0 where frutta0.frid=? Hibernate: select vermi0.fid as fid__, vermi0.vid as v_id__, vermi0.vid as vid0, vermi0.nome as nome0, vermi0.fid as fid0 from vermi vermi0 where vermi0.fid=? Hibernate: select frutto0.fid as fid0, frutto0.tipo as tipo0, frutto0.colore as colore0, frutto0.ismarcio as ismarcio0, frutto0.tipologiamela as tipologi50, frutto0.colorebollino as coloreb60 from frutta frutto0 where frutto0.fid=? Hibernate: select vermi0.fid as fid__, vermi0.vid as v_id__, vermi0.vid as vid0, vermi0.nome as nome0, vermi0.fid as fid0 from vermi vermi0 where vermi0.fid=? Hibernate: update frutta set frid=? where fid=?
Nel listato 8 creiamo un Fruttivendolo, un Frutto e l'associazione tra i due, nel listato 10 invece preleviamo tramite chiave primaria un Fruttivendolo, sempre con chiave primaria preleviamo un Frutto e quindi creiamo la relazione tra i due. La cosa importante da notare è che per creare la relazione tra un oggetto Frutto e un oggetto Fruttivendolo le due istanze devono essere già state persistite ecco il perché di sess.save(mela) del listato 8. I listati 9 e 11 sono le istruzioni sql (auto esplicative) generate da Hibernate. Interessanti sono gli aspetti legati alla cancellazione di un fruttivendolo. Da tener presente che un Verme deve continuare ad esistere a prescindere dalla cancellazione del Frutto a cui era associato e che, invece, un Frutto deve “morire” alla cancellazione di un Fruttivendolo (notare gli attributi cascade nei listati 5 e 7 in corrispondenza delle collezioni vermi e frutta). Proviamo ora, listato 12, a creare un fruttivendolo con 2 frutti e ad uno di questi frutti associamo un verme. Listato 12 - Fruttivendolo + 2 Frutti + 1 Verme sess = sf.openSession(c); Mela mela= new Mela();
mela.setColore("Blue");
mela.setMarcio(false);
mela.setTipologiaMela("PincoPalla");
Mela mela2= new Mela();
mela2.setColore("Verde");
mela2.setMarcio(false);
mela2.setTipologiaMela("Dura");
Verme v=new Verme();
v.setNome("Peppino");
v.setFrutto(mela2);
sess.save(v);
sess.save(mela);
Fruttivendolo fr=new Fruttivendolo();
fr.setRagioneSociale("Raul e Co");
Vector frutta=new Vector();
sess.save(fr);
frutta.add(mela);
frutta.add(mela2);
fr.setFrutta(frutta);
sess.flush();
c.commit();
Listato 13- Cancellazione (a cascata) di un fruttivendolo Fruttivendolo f=(Fruttivendolo) sess.load(Fruttivendolo.class,new Integer(8));
c.commit();
sess.close();
sess = sf.openSession(c);
sess.delete(f);
sess.flush();
Ora tramite il codice del listato 13 cancelliamo il fruttivendolo e si nota, osservando l'SQL riportato nel listato 14, che viene cancellato il Fruttivendolo, i due frutti e viene dereferenziato il verme associato al frutto cancellato. Listato 14- SQL prodotto da Hibernate relativo al listato 13
Hibernate: update frutta set frid=null where frid=? Hibernate: update vermi set fid=null where fid=? Hibernate: delete from frutta where fid=? Hibernate: delete from frutta where fid=? Hibernate: delete from fruttivendoli where frid=?
I Contadino e Fruttivendolo: relazione molti a molti Dopo aver mappato Contadino, l'ultima classe rimasta in sospeso (vedi listato 15), si focalizza l'attenzione sulla relazione che lega quest'ultima entità con il Fruttivendolo. Un Fruttivendolo è rifornito da molti contadini e un Contadino rifornisce molti Fruttivendoli, come è facilmente intuibile questa è una relazione molti a molti. Dal punto di vista relazionale la relazione molti a molti si realizza tramite la creazione di una tabella contenete le coppie di chiavi primarie delle sue entità associate, se un determinato Contadino non è legato con un determinato Fruttivendolo non esiste una riga nella tabella relazione contenete le chiavi primarie delle due entità. Dal punto di vista Java, invece, la relazione è costituita da due collezioni: una, quella dei contadini, presente come attributo nella classe Fruttivendolo e l'altra, quella dei fruttivendoli, presente come attributo nella classe Contadino. Listato 15- File di mapping relativo alla classe Contadino (Contadino.hbm.xml) <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <!-- lc.hibernate.Contadino root --> <class name="lc.hibernate.Contadino" table="contadini"> <id name="id" type="int" unsaved-value="0"> <generator class="identity"/> </id>
<bag name="fruttivendoli" table="fruttivendoli_contadini" cascade="none"> <key column="c_id"/> <many-to-many class="lc.hibernate.Fruttivendolo" column="fr_id"/> </bag> <property name="ragioneSociale" column="ragione_sociale" type="string"/> </class> </hibernate-mapping>
La relazione è bidirezionale quindi è possibile controllarla da entrambe le entità, le associazioni tra Contadino e Fruttivendolo si gestiscono esattamente come si è gestita la relazione tra fruttivendoli e frutta (vedi listati precedenti) con la differenza che l'attributo cascade in entrambe le direzione ha come valore “none” di conseguenza le cancellazioni di un'entità comporteranno, come unico effetto a cascata, la rimozione dell'entità della sola tabella di relazione. A titolo d'esempio nel listato 16 si riporta il codice relativo all'associazione tra due oggetti delle classi in esame entrambi precedentemente persistiti.
Listato 16- Associazione tra Contadino e Frutto sess = sf.openSession(c);
Fruttivendolo geppetto=(Fruttivendolo) sess.load(Fruttivendolo.class,new Integer(12));
Contadino piero=(Contadino) sess.load(Contadino.class,new Integer(1));
geppetto.getContadini().add(piero);
sess.flush();
Listato 17 parte di SQL relativo al codice riportato nel listato16 Hibernate: select contadino0.id as id0, contadino0.ragionesociale as ragione20 from contadini contadino0 where contadino0.id=? Hibernate: select fruttivend0.cid as cid__, fruttivend0.frid as fr_id__, fruttivend1.frid as frid0, fruttivend1.ragionesociale as ragione20 from fruttivendolicontadini fruttivend0 inner join fruttivendoli fruttivend1 on fruttivend0.frid=fruttivend1.frid where fruttivend0.cid=? Hibernate: insert into fruttivendolicontadini (frid, cid) values (?, ?)
Listato 18 - SQL usato per creare le tabelle del db relazionale
# # Table structure for table 'contadini' #
CREATE TABLE contadini ( id int(10) unsigned NOT NULL autoincrement, ragionesociale varchar(45) NOT NULL default '', PRIMARY KEY (id) ) TYPE=InnoDB;
# # Table structure for table 'frutta' #
CREATE TABLE frutta ( fid int(10) unsigned NOT NULL autoincrement, tipo varchar(10) NOT NULL default '', colore varchar(15) default '0', ismarcio tinyint(1) unsigned NOT NULL default '0', tipologiamela varchar(15) default NULL, colorebollino varchar(15) default NULL, frid int(10) unsigned default NULL, PRIMARY KEY (fid) ) TYPE=InnoDB;
# # Table structure for table 'fruttivendoli' #
CREATE TABLE fruttivendoli ( frid int(10) unsigned NOT NULL autoincrement, ragionesociale varchar(45) NOT NULL default '', PRIMARY KEY (frid) ) TYPE=InnoDB;
# # Table structure for table 'fruttivendoli_contadini' #
CREATE TABLE fruttivendolicontadini ( frid int(10) unsigned NOT NULL default '0', cid int(10) unsigned NOT NULL default '0', PRIMARY KEY (frid,c_id) ) TYPE=InnoDB;
# # Table structure for table 'vermi' #
CREATE TABLE vermi ( vid int(11) unsigned NOT NULL autoincrement, nome varchar(20) default NULL, fid int(11) unsigned default NULL, PRIMARY KEY (vid), KEY fid (fid) ) TYPE=InnoDB;
.
Conclusioni
In questa seconda parte si è completata l'operazione di mapping del modello d'esempio e si è analizzato il come Hibernate gestisce le relazioni tra oggetti persistiti. Nella prossima parte si vedrà quali sono e come usare gli strumenti che il framework in analisi mette a disposizione per interrogare l'insieme degli oggetti persistiti andando ad analizzare quali sono i parametri a su cui possiamo agire per ottimizzare le prestazioni. Verrà mostrato inoltre come è possibile integrare hibernate con Jboss ed Spring Framework. Riferimenti [1] Introduzione ad Hibernate parte I : http://www.mokabyte.it/2005/02/hibernate-1.htm [2] Hibernate Official Site, http://www.hibernate.org
IIparte – Le relazioni
In questo articolo si completa l'operazione di mapping del modello in esame e ne vengono messi in evidenza gli aspetti legati alle relazioni tra le classi.
