Hibernate supporta le tre strategie di base per il mappaggio dell'ereditarietà.
una tabella per un'intera gerarchia di classi
una tabella per ogni sottoclasse
una tabella per ogni classe concreta (con qualche limitazione)
È anche possibile utilizzare differenti strategie di mappaggio per rami differenti della stessa gerarchia di ereditarietà, ma questo scenario è soggetto alle stesse limitazioni dei mappaggi "una tabella per ogni classe concreta" (che vedremo nel seguito). Hibernate non supporta la possibilità di mischiare mappaggi <subclass> e <joined-subclass> nello stesso elemento <class>.
Immaginiamo di avere un'interfaccia Payment, con le seguenti classi che la implementino: CreditCardPayment, CashPayment, ChequePayment. Il mappaggio a "una tabella per gerarchia" apparirebbe così:
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> ... </subclass> <subclass name="CashPayment" discriminator-value="CASH"> ... </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> ... </subclass> </class>
È quindi necessaria esattamente una tabella. C'è una importante limitazione in questa strategia di mappaggio: le colonne dichiarate dalle sottoclassi non possono avere vincoli NOT NULL.
Il mappaggio a "una tabella per sottoclasse" apparirebbe così:
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="AMOUNT"/> ... <joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <key column="PAYMENT_ID"/> ... </subclass> <joined-subclass name="CashPayment" table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> ... </subclass> <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> <key column="PAYMENT_ID"/> ... </subclass> </class>
Sono quindi richieste quattro tabelle. Le tre tabelle di sottoclasse hanno associazioni di chiave primaria con la tabella di superclasse (cosicché il modello relazionale è in realtà una associazione uno-a-uno).
È importante notare che l'implementazione di Hibernate della strategia "una tabella per sottoclasse" non richiede una colonna discriminatore. Altri sistemi di mappaggio oggetto/relazione usano una implementazione differente di questa strategia, che richiede una colonna di discriminazione del tipo nella tabella della superclasse. L'approccio assunto da Hibernate è molto più difficile da implementare, ma più corretto da un punto di vista relazionale.
Per ognuna di queste strategie di mappaggio, una associazione polimorfica a Payment viene mappata usando <many-to-one>.
<many-to-one name="payment" column="PAYMENT" class="Payment"/>
La strategia "una tabella per classe concreta" è molto differente.
<class name="CreditCardPayment" table="CREDIT_PAYMENT"> <id name="id" type="long" column="CREDIT_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CREDIT_AMOUNT"/> ... </class> <class name="CashPayment" table="CASH_PAYMENT"> <id name="id" type="long" column="CASH_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CASH_AMOUNT"/> ... </class> <class name="ChequePayment" table="CHEQUE_PAYMENT"> <id name="id" type="long" column="CHEQUE_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CHEQUE_AMOUNT"/> ... </class>
Sono state necessarie tre tabelle. Si noti che non menzioniamo esplicitamente da nessuna parte l'interfaccia Payment. Invece, usiamo il polimorfismo implicito di Hibernate. Notate anche che le proprietà di Payment sono state mappate in ognuna delle sue sottoclassi.
In questo caso, una associazione polimorfica a Payment viene mappata usando <any>.
<any name="payment" meta-type="class" id-type="long"> <column name="PAYMENT_CLASS"/> <column name="PAYMENT_ID"/> </any>
Sarebbe meglio se definissimo uno UserType come meta-tipo, per gestire il mappaggio dalle stringhe di discriminazione verso la sottoclasse di Payment.
<any name="payment" meta-type="PaymentMetaType" id-type="long"> <column name="PAYMENT_TYPE"/> <!-- CREDIT, CASH or CHEQUE --> <column name="PAYMENT_ID"/> </any>
C'è ancora una cosa da considerare riguardo a questo mappaggio. Poiché le sottoclassi sono mappate ognuna nel proprio elemento <class> (e poiché Payment è solo un'interfaccia), ognuna delle sottoclassi potrebbe essere parte di un'altra gerarchia di ereditarietà di tipo "tabella per classe" o "tabella per sottoclasse"! (Ed è comunque possibile lanciare interrogazioni polimorfiche sull'interfaccia Payment).
<class name="CreditCardPayment" table="CREDIT_PAYMENT"> <id name="id" type="long" column="CREDIT_PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="CREDIT_CARD" type="string"/> <property name="amount" column="CREDIT_AMOUNT"/> ... <subclass name="MasterCardPayment" discriminator-value="MDC"/> <subclass name="VisaPayment" discriminator-value="VISA"/> </class> <class name="NonelectronicTransaction" table="NONELECTRONIC_TXN"> <id name="id" type="long" column="TXN_ID"> <generator class="native"/> </id> ... <joined-subclass name="CashPayment" table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> <property name="amount" column="CASH_AMOUNT"/> ... </joined-subclass> <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> <key column="PAYMENT_ID"/> <property name="amount" column="CHEQUE_AMOUNT"/> ... </joined-subclass> </class>
Anche in questo caso non menzioniamo Payment esplicitamente. Se eseguiamo un'interrogazione sull'interfaccia Payment - ad esempio, from Payment - Hibernate resistuisce automaticamente istanze di CreditCardPayment (e delle sue sottoclassi, poiché anch'esse implementano Payment), CashPayment e ChequePayment ma non istanze di NonelectronicTransaction.
Hibernate assume che un'associazione corrisponda esattamente ad una colonna di chiave esterna. Associazioni multiple per chiave esterna sono tollerate (potete avere bisogno di specificare inverse="true" o insert="false" update="false"), ma non c'è modo di mappare una associazione a più chiavi esterne. Questo significa che:
quando un'associazione viene modificata, è sempre la stessa chiave esterna che viene aggiornata
quando un'associazione è risolta in maniera differita ("fetched lazily"), viene usata una singola interrogazione sulla base di dati
quando un'associazione è risolta in maniera immediata ("fetched eagerly"), può venire risolta usando una singola join esterna
In particolare, questo implica che le associazioni polimorfiche uno-a-molti verso classi mappate usando la strategia "tabella per classe concreta" non sono supportate. (Risolvere queste associazioni implicherebbe effettuare interrogazioni o join multiple.)
La tabella seguente mostra le limitazioni dei mappaggi a "tabella per classe concreta" e del polimorfismo implicito in Hibernate.
Tabella 8.1. Funzionalità dei mappaggi di ereditarietà
Strategia di ereditarietà | Molti-a-uno polimorfico | Uno-a-uno polimorfico | Uno-a-molti polimorfico | Molti-a-molti polimorfico | load()/get() polimorfiche | Interrogazioni polimorfiche | Join polimorfici | Risoluzione con join esterna |
---|---|---|---|---|---|---|---|---|
"tabella per gerarchia" | <many-to-one> | <one-to-one> | <one-to-many> | <many-to-many> | s.get(Payment.class, id) | from Payment p | from Order o join o.payment p | supportata |
"tabella per sottoclasse" | <many-to-one> | <one-to-one> | <one-to-many> | <many-to-many> | s.get(Payment.class, id) | from Payment p | from Order o join o.payment p | supportata |
"tabella per classe concreta" (polimorfismo implicito) | <any> | non supportata | non supportata | <many-to-any> | usando una query | from Payment p | non supportata | non supportata |