Uno dei problemi dell’Object-Relational Impedance Mismatch (
tratto nell’articolo raggiungibile cliccando sul seguente link: http://lingegnereinformatico.blogspot.it/2014/05/object-relational-impedance-mismatch.html
) è dovuto all’inesistenza del concetto di ereditarietà nel mondo relazionale.
Essendo un elemento fondamentale per il mondo Object-Oriented, è interessante
capire come un ORM come Hibernate permette di risolvere la differenza
concettuale senza impelagarsi in complesse operazioni di conversione.
Si supponga di essere in un contesto universitario, nel
quale si vogliono rappresentare i concetti di Professore e Studente. Per semplicità
si considerino due caratteristiche comuni: il nome e cognome tipiche di una
persona. L’attributo tipico di uno studente è il numero di matricola, mentre
per un professore è il salario. Questi aspetti si rappresentano attraverso il
concetto di ereditarietà. Un diagramma UML è mostrato in Figura
1.
Figura 1 - Persona, Studente e Professore
Passando alla fase di implementazione (il linguaggio
utilizzato nel presente articolo è il Java), si otterranno tre classi, di cui Studente
e Professore sfrutteranno l’“extends” per avere a disposizione gli attributi
della classe padre Persona (Il codice è volutamente omesso poiché banale).
A questo punto risulta interessante capire come si può
trasformare il concetto di ereditarietà in maniera da renderlo appetibile al
modello relazionale attraverso l’utilizzo dell’ORM per eccellenza cioè
Hibernate.
In generale, la corrispondenza tra classi e tabelle nelle
casistiche più semplici è di uno a uno. Nel caso dell’ereditarietà il mapping
può essere effettuato seguendo tre differenti strategie:
- Una tabella per l’intera gerarchia di classi
Viene generata una sola tabella per l’intera gerarchia.
Prendendo come riferimento la superclasse, viene creata una tabella che avrà un
numero di colonne pari al numero di attributi della superclasse e tutte le
sottoclassi più una. Quest’ultima è la colonna utilizzata come discriminante.
Nel caso specifico di Hibernate è sufficiente agire nel file
di mapping della superclasse che conterrà i metadati per accedere alle
sottoclassi. Gli elementi XML che vengono utilizzati sono:
Discriminator per mappare la colonna che funge da discriminante. Va definito subito dopo il tag id.
Subclass per mappare le sottoclassi
Discriminator per mappare la colonna che funge da discriminante. Va definito subito dopo il tag id.
Subclass per mappare le sottoclassi
Questa strategia presenta un limite: le colonne che
scaturiscono dagli attributi delle sottoclassi non possono avere il vincolo NOT
NULL. Il motivo di ciò è dovuto al fatto che una singola riga potrebbe essere o
uno studente o un professore. Nel caso dello studente l’attributo salario non
avrà senso e quindi avrà valore NULL. Stesso discorso per il professore con l’attributo
matricola.
Il file di mapping persona.hbm.xml sarà:
<hibernate-mapping>
<class name="model.Persona"
table="PERSONA">
<id name="id"
type="java.lang.Integer">
<column name="ID" />
<generator class="assigned" />
</id>
<discriminator column="tipo"
type="string"/>
<property name="nome"
type="java.lang.String">
<column name="NOME" />
</property>
<property name="cognome"
type="java.lang.String">
<column name="COGNOME" />
</property>
<subclass name="model.Studente"
discriminator-value="S">
<property name="matricola" column="matricola"
type="java.lang.Integer"/>
</subclass>
<subclass name="model.Professore" discriminator-value="P">
<property name="salario" column="salario"
type="java.lang.Integer"/>
</subclass>
</class>
</hibernate-mapping>
2. Una tabella per ogni classe
Viene generate una tabella per la superclasse e una per ogni
sottoclasse. Ogni sottoclasse avrà una colonna che sarà chiave primaria e che
avrà una foreign key verso la chiave primaria della superclasse. In Hibernate i
file di mapping associati alle sottoclassi utilizzeranno l’elemento xml <joined-subclass
>.
Persona.hbm.xml
<hibernate-mapping>
<class name="model.Persona"
table="PERSONA">
<id name="id"
type="java.lang.Integer">
<column name="ID" />
<generator class="assigned" />
</id>
<property name="nome"
type="java.lang.String">
<column name="NOME" />
</property>
<property name="cognome"
type="java.lang.String">
<column name="COGNOME" />
</property>
</class>
</hibernate-mapping>
Studente.hbm.xml
<hibernate-mapping>
<joined-subclass name="model.Studente" extends="model.Persona" table="STUDENTE" lazy="false">
<key>
<column name="ID" />
</key>
<property name="matricola"
type="int">
<column name="MATRICOLA" />
</property>
</joined-subclass>
</hibernate-mapping>
Professore.hbm.xml
<hibernate-mapping>
<joined-subclass name="model.Professore" extends="model.Persona" table="PROFESSORE" lazy="false">
<key>
<column name="ID" />
</key>
<property name="salario"
type="int">
<column name="SALARIO" />
</property>
</joined-subclass>
</hibernate-mapping>
3. Una tabella per ogni classe concreta
Viene generata una tabella per ogni classe concreta della
gerarchia. In ogni tabella vengono
definite tutte le proprietà della classe, comprese quelle ereditate. Nel caso specifico, si ipotizzi che la classe
Persona sia una classe astratta. In tal caso, si avranno due tabelle
corrispondenti alle due classi concrete: Studente e Professore. Entrambe
avranno gli attributi della superclasse insieme a quelli propri. Nel caso in
cui la gerarchia non sia esaustiva vorrà dire che la classe Persona non è
astratta e sarà necessario creare anche una tabella per essa.
Per quanto riguarda il mapping di Hibernate è necessario
distinguere il caso di gerarchia non completa da quello di gerarchia completa
attraverso l’attributo abstract (che può essere true o false) per l'elemento <class>.
Anche in questo caso, i metadati per il mapping delle
sottoclassi vengono indicati nel file di mapping della superclasse. Vengono
utilizzati elementi <union-subclass>
per le sottoclassi.
<hibernate-mapping>
<class name="model.Persona"
table="PERSONA">
<id name="id"
type="java.lang.Integer">
<column name="ID" />
<generator class="assigned" />
</id>
<property name="nome"
type="java.lang.String">
<column name="NOME" />
</property>
<property name="cognome"
type="java.lang.String">
<column name="COGNOME" />
</property>
<union-subclass name="model.Studente" table="studente">
<property name="matricola" column="matricola"
type="java.lang.Integer"/>
</union-subclass>
<union-subclass name="model.Professore" table="professore">
<property name="salario" column="salario"
type="java.lang.Integer"/>
</union-subclass>
</class>
</hibernate-mapping>
La scelta della strategia da adottare può essere fatta
considerando diversi fattori. Al seguito si riporta una tabella dei fattori
determinanti e il loro impatto per le tre strategie.
Fattore
da considerare
|
Strategia
1
|
Strategia
2
|
Strategia
3
|
Facilità di implementazione
|
Semplice
|
Difficile
|
Medio
|
Facilità di accesso ai dati
|
Semplice
|
Medio
|
Semplice
|
Accoppiamento
|
Molto elevato
|
Basso
|
Elevato
|
Velocità nell’accesso ai dati
|
Veloce
|
Medio
|
Veloce
|
Supporto al polimorfismo
|
Medio
|
Alto
|
Basso
|
0 commenti:
Posta un commento