Concludiamo l‘analisi del design della persistenza con Hibernate, rivedendo il modello di dominio e soffermandoci su alcune tipologie di relazione tra le classi e il loro mapping al modello relazionale tramite di Hibernate.
Introduzione
Nel presente articolo concludiamo lo studio del modello di dominio del nostro sistema con l’ormai noto obiettivo di mostrare come esso si possa mappare su una struttura relazionale con l’ausilio di Hibernate. Riportiamo, in fig. 1, per l’ennesima volta lo schema UML delle classi del nostro modello per comodità del lettore.
Figura 1 – Il modello di dominio.
Nel modello ci sono altre relazioni tra le classi piuttosto comuni nel mondo ad oggetti, e quindi interessanti ai fini della mappatura sulla struttura relazionale, che non sono ancora state trattate negli articoli precedenti.
Una di queste relazioni è la composizione, che affronteremo nel paragrafo seguente. Torneremo poi brevemente sulle classi di associazione, argomento già trattato in precedenza, solo per puntualizzare il concetto e mostrare come non ci siano sostanziali differenze nell’implementazione con Hibernate.
La composizione
La composizione è uno dei concetti di base del paradigma della programmazione ad oggetti. La composizione è una relazione di tipo”whole-part”, “parte-intero” diremmo noi, nel quale un oggetto rappresenta l’intero e un altro una sua parte, quindi un suo componente.
La relazione di composizione viene rappresentata in UML con una linea che va dalla “parte” all'”intero” che termina con un diamante scuro, come ad esempio nella relazione tra le classi Famiglia e Regina.
Una relazione di composizione associa in maniera molto forte due oggetti che risultano legati indissolubilmente. Il ciclo di vita dell’oggetto “componente” e dell’oggetto “contenitore” è strettamente legato, tant’è che, se viene distrutta l’istanza dell’intero, vengono a morire anche le sue parti. La composizione è quindi un legame molto stretto tra oggetti ed esprime un vincolo molto forte.
La composizione è una forma di associazione simile all’aggregazione, relazione che in UML si esprime con una linea terminante con un diamante non riempito. Nell’aggregazione l’istanza dell’oggetto componente ha vita propria e non è strettamente legata all’oggetto contenitore. Nella composizione invece la parte è creata e distrutta insieme all’intero oggetto.
A differenza dell’ereditarietà che è una relazione di tipo is-a (“a car is a vehicle”, un’automobile è un veicolo, per cui potrei dire che la classe Automobile estende la classe Veicolo), la composizione è una relazione di tipo has-a (“a car has an engine”, un’automobile ha un motore, per cui potrei dire che la classe Automobile ha un componente di tipo Motore).
La composizione è molto usata nella modellazione dei sistemi ad oggetti ed è quasi sempre preferibile all’ereditarietà anche se quest’ultima è forse più conosciuta e quindi utilizzata molto e a sproposito. Comunque, senza addentrarci in discorsi troppo approfonditi riguardo l’analisi ad oggetti che esulano dai nostri obiettivi, vediamo come Hibernate ci aiuta a modellare la composizione nel modello relazionale. Prendiamo quindi come riferimento la relazione tra Famiglia e Regina di fig. 2.
Figura 2 – Relazione di composizione.
In questo caso un’istanza della classe Regina è una parte dell’intero costituito dall’istanza della classe Famiglia. La relazione è di tipo unidirezionale dalla Famiglia verso la Regina come spiegato in [3]. Per ciò che riguarda l’implementazione in Java la cosa è piuttosto semplice e si traduce nelle seguenti due classi (la classe Famiglia era stata già presentata in [4]):
Famiglia
public class Famiglia { private Apiario apiario; private Integer id; private Integer position; private String notes; private Regina regina; public Famiglia() { } public Famiglia(Apiario a) { this.apiario = a; } ... ... }
Regina
public class Regina { public Regina(){ } private String rAttr1; private String rAttr2; ... ... //metodi getter/setter ... ... }
Nella classe Famiglia è stato aggiunto un riferimento alla classe Regina, mentre quest’ultima non ha un riferimento alla classe Famiglia vista la relazione monodirezionale tra le due.
Per capire come si possa modellare questa relazione in Hibernate, bisogna osservare che l’oggetto della classe Regina, essendo strettamente dipendente dalla classe Famiglia, non ha un suo ciclo di vita e una sua identità. Per cui non è necessario modellarla come una classe di entità ma piuttosto come un cosiddetto value-type. Un oggetto che costituisce un value-type è strettamente legato all’oggetto di cui è componente, per cui non ha una propria identità neanche a livello di database. Il suo ciclo di vita è legato all’oggetto che lo contiene e non può condividere il proprio riferimento con altri oggetti. Per cui nasce e muore con l’oggetto contenitore e i suoi attributi vengono modellati nella stessa tabella dell’entità che lo contiene. Supponendo quindi di trattare la classe Regina non come un entità del modello ma come un value-type , è possibile modellare la relazione tra le due classi con il seguente file di mapping di Hibernate:
Famiglia.hbm.xml
"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> not-null="true"/> not-null="true"/>
Nel file di mapping della classe Famiglia già presentato in [4] è stata aggiunta la sezione che va ad esprimere proprio il concetto appena descritto in cui un’istanza della classe Regina va a costituire un value-type della classe Famiglia. Quindi l’attributo regina della classe Famiglia avrà come tipo la classe Regina che non è quindi modellata come una entità indipendente con una propria identità univoca. L’istanza della classe Regina componente dell’istanza della classe Famiglia non potrà condividere il proprio riferimento con nessun altro oggetto e sarà quindi indissolubilmente legato all’istanza della classe Famiglia di cui è parte.
A livello di database l’istanza della classe Regina non ha una propria identità e quindi gli attributi della classe Regina diventeranno nient’altro che colonne della tabella FAMILY. Quando la riga della tabella FAMILY corrispondente all’entità Famiglia sarà cancellata, allora anche le colonne corrispondenti alla Regina componente saranno cancellate, e ciò esprime la relazione di composizione appena descritta.
Le classi di associazione
Un’altra relazione interessante presente nel nostro modello di dominio è l’associazione tra le classi Apicoltore ed Apiario. In questo caso come vediamo dal diagramma UML di fig. 3 l’associazione è espressa mediante una classe che prende il nome di classe di associazione.
Figura 3 – Classe di associazione.
Una classe di associazione è usata , come dice il suo stesso nome, per modellare l’associazione tra due classi con una nuova classe. Le classi di associazione sono utilizzate quando si vuole esprimere il concetto che l’associazione stessa ha dei propri attributi. Quando un’associazione tra due classi ha delle caratteristiche peculiari indipendentemente dalle classi che essa collega, allora ha un senso una classe di associazione. Una classe di associazione si rappresenta in UML con un simbolo di classe collegata a una associazione da una linea tratteggiata.
Modellare una classe di associazione nel modello relazionale è abbastanza semplice, basta trasformare la relazione in due singole relazioni one-to-many con le classi collegate dalla classe di associazione come rappresentato in fig. 4.
Figura 4 – Trasformazione in due associazioni.
Il caso era già stato trattato nell’articolo [5] a cui rimandiamo, e quindi sarebbe una inutile ripetizione ripresentare qui la stessa trattazione dal momento che, a prescindere, dalla tecnologia implementativa il concetto è lo stesso.
Molto brevemente però si può quindi dire che il metodo più semplice è quello di considerare la classe di associazione una entità del modello e definire due relazioni di tipo one-to-many tra questa e le due classi legate dall’associazione. In pratica una relazione many-to-many viene scomposta in due associazioni one-to-many che si modellano con Hibernate come già descritto in [4].
Nel database ciò si traduce mappando le tre entità con opportune tabelle; la tabella che rappresenta nel database l’entità Intervento costituisce quindi una tabella di relazione tra le entità Apicoltore ed Apiario.
Conclusioni
Negli articoli dedicati all’implementazione dello strato di persistenza del sistema con Hibernate abbiamo cercato di mostrare come sia possibile effettuare il mapping tra due modelli, quello relazionale e quello a oggetti, che di per se’ sono piuttosto differenti. Le varie relazioni presenti nel modello di dominio, ereditarietà, composizione, associazioni di vario tipo, ci hanno permesso di presentare esempi di casi pratici comuni e utili alla comprensione di questo delicato aspetto nella realizzazione di un applicazione. Lo scopo non era tanto di spiegare la tecnologia di Hibernate nei minimi dettagli, compito assolto molto bene dai testi citati nei riferimenti, ma piuttosto di fornire spunti e punti di vista che servano come base futura per coloro che vorranno cimentarsi nella realizzazione di uno strato di persistenza secondo la metodologia ORM.
Riferimenti
[1] Hibernate Reference Documentation 3.3.1, Copyright © 2004 Red Hat Middleware, LLC.
[2] Christian Bauer – Gavin King, “Java Persistence with Hibernate”, 2006