Nel precedente articolo Mapping di Gerarchie con Hibernate (parte 1) abbiamo parlato le strategie di mapping per le gerarchie MappingSuperclass e Single Table. In questo post proseguiamo descrivendo le rimanenti strategie Join Table e Table-Per-Class.
Join Table
Utilizzando tale strategia ogni classe della gerarchia è mappata in una tabella. Le diverse entità condividono una sola colonna, l’identificatore id
dichiarato nella classe Asset
, che è utilizzata come campo di join tra le tabelle per recuperare il record completo.
L’implementazione della strategia si ottiene valorizzando opportunamente l’annotazione @Inheritance
sulla classe Asset
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@Entity @Inheritance(strategy = InheritanceType.JOINED) public class Asset { @Id @GeneratedValue private Long id; ... } @Entity public class Laptop extends Asset { private String model; ... } @Entity public class MobilePhone extends Asset { private String number; ... } |
main()
associato al modulo ed analizziamo l’output generato con tale strategia:
Innanzitutto è droppato il database e vengono rigenerate le tabelle Asset
, Laptop
e MobilePhone
. Come ci si aspettava tutte le tabella hanno la colonna id
di tipo primary key ed inoltre è generato un vincolo di foreign key tra le colonne id
di Laptop
e MobilePhone
, e la colonna id
di Asset.
1 2 3 4 5 6 7 8 9 10 11 |
Hibernate: drop table PUBLIC.Asset if exists Hibernate: drop table PUBLIC.Laptop if exists Hibernate: drop table PUBLIC.MobilePhone if exists Hibernate: drop sequence if exists PUBLIC.hibernate_sequence Hibernate: create sequence PUBLIC.hibernate_sequence start with 1 increment by 1 Hibernate: create table PUBLIC.Asset (id bigint not null, primary key (id)) Hibernate: create table PUBLIC.Laptop (model varchar(255), id bigint not null, primary key (id)) Hibernate: create table PUBLIC.MobilePhone (number varchar(255), id bigint not null, primary key (id)) Hibernate: alter table PUBLIC.Laptop add constraint FKlu62u76fb4xsng29paqub5x04 foreign key (id) references PUBLIC.Asset Hibernate: alter table PUBLIC.MobilePhone add constraint FKj4qt4nupi36ej0k4kvkrv0c77 foreign key (id) references PUBLIC.Asset |
Il salvataggio di 4 record genera in realtà 8 inserimenti. Per ogni record infatti è generata una riga sia sulla tabella Asset
che sulla tabella specifica del tipo di asset.
1 2 3 4 5 6 7 8 9 10 11 12 |
Hibernate: call next value for PUBLIC.hibernate_sequence Hibernate: call next value for PUBLIC.hibernate_sequence Hibernate: call next value for PUBLIC.hibernate_sequence Hibernate: call next value for PUBLIC.hibernate_sequence Hibernate: insert into PUBLIC.Asset (id) values (?) Hibernate: insert into PUBLIC.Laptop (model, id) values (?, ?) Hibernate: insert into PUBLIC.Asset (id) values (?) Hibernate: insert into PUBLIC.MobilePhone (number, id) values (?, ?) Hibernate: insert into PUBLIC.Asset (id) values (?) Hibernate: insert into PUBLIC.Laptop (model, id) values (?, ?) Hibernate: insert into PUBLIC.Asset (id) values (?) Hibernate: insert into PUBLIC.MobilePhone (number, id) values (?, ?) |
Asset
.
1 2 3 4 5 6 7 8 |
List<Asset> assets = session.createQuery("from Asset").getResultList(); System.out.println(assets.size() + " asset(s) found: "); for (Iterator<Asset> iter = assets.iterator(); iter.hasNext();) { Asset asset = iter.next(); System.out.println( asset ); } |
La query generata da Hibernate e ben più complicata delle precedenti in quanto deve essere eseguita una join che coinvolge tutte le tabelle.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Hibernate: select asset0_.id as id1_0_, asset0_1_.model as model1_1_, asset0_2_.number as number1_2_, case when asset0_1_.id is not null then 1 when asset0_2_.id is not null then 2 when asset0_.id is not null then 0 end as clazz_ from PUBLIC.Asset asset0_ left outer join PUBLIC.Laptop asset0_1_ on asset0_.id=asset0_1_.id left outer join PUBLIC.MobilePhone asset0_2_ on asset0_.id=asset0_2_.id 4 asset(s) found: Asset id 1 is a laptop model Acer Asset id 2 is a mobile phone number 555-060606 Asset id 3 is a laptop model Compaq Asset id 4 is a mobile phone number 555-090909 |
In generale il numero di join necessarie aumenta con il livello della gerarchia dove si intende eseguire la query, in quanto devono essere coinvolte tutte le tabelle discendenti rispetto al punto di select. Ovviamente la join non è necessaria se si esegue la select direttamente sulle entità figli: Laptop
o MobilePhone
.
Possiamo quindi concludere dicendo che con tale strategia le performance potrebbero essere scadenti nel caso di recupero di un gran numero di record con query che coinvolgono le classi gerarchicamente più alte.
Table Per Class
Con questo tipo di strategia è generata una tabella per ogni classe della gerarchia in cui sono presenti tutte le colonne necessarie a mappare tutte le proprietà ereditate. Molto simile alla strategia MappedSuperclass con la differenza che è generata una tabella anche per la classe genitrice. Inoltre rispetto alla strategia Join Table non esiste relazione tra l’entità padre e le figlie, al punto che è possibile inserire un record Asset
che non è né un Laptop
né un MobilePhone
.
Eseguiamo il main()
associato al modulo ed analizziamo l’output generato con tale strategia:
Come ci aspettavamo sono generate tre tabelle, una per Asset
con la sola colonna id,
una per Laptop
e MobilePhone
in cui è presente anche la colonna id
ereditata. Inoltre non ci è alcuna foreign key tra le tabelle figlie e la padre.
1 2 3 4 5 6 7 8 |
Hibernate: drop table PUBLIC.Asset if exists Hibernate: drop table PUBLIC.Laptop if exists Hibernate: drop table PUBLIC.MobilePhone if exists Hibernate: drop sequence if exists PUBLIC.hibernate_sequence Hibernate: create sequence PUBLIC.hibernate_sequence start with 1 increment by 1 Hibernate: create table PUBLIC.Asset (id bigint not null, primary key (id)) Hibernate: create table PUBLIC.Laptop (id bigint not null, model varchar(255), primary key (id)) Hibernate: create table PUBLIC.MobilePhone (id bigint not null, number varchar(255), primary key (id)) |
In questo caso oltre ai 4 record, due per tipologia di asset, inseriamo anche un record generico sulla tabella Asset
.
1 2 3 4 5 6 7 8 9 10 |
Hibernate: call next value for PUBLIC.hibernate_sequence Hibernate: call next value for PUBLIC.hibernate_sequence Hibernate: call next value for PUBLIC.hibernate_sequence Hibernate: call next value for PUBLIC.hibernate_sequence Hibernate: call next value for PUBLIC.hibernate_sequence Hibernate: insert into PUBLIC.Asset (id) values (?) Hibernate: insert into PUBLIC.Laptop (model, id) values (?, ?) Hibernate: insert into PUBLIC.MobilePhone (number, id) values (?, ?) Hibernate: insert into PUBLIC.Laptop (model, id) values (?, ?) Hibernate: insert into PUBLIC.MobilePhone (number, id) values (?, ?) |
Asset
comporta la generazione un una serie di UNION
tra tutte le entità della gerarchia.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
Hibernate: select asset0_.id as id1_0_, asset0_.model as model1_1_, asset0_.number as number1_2_, asset0_.clazz_ as clazz_ from ( select id, null as model, null as number, 0 as clazz_ from PUBLIC.Asset union all select id, model, null as number, 1 as clazz_ from PUBLIC.Laptop union all select id, null as model, number, 2 as clazz_ from PUBLIC.MobilePhone ) asset0_ 5 asset(s) found: it.javaboss.tableperclass.Asset@435871cb Asset id 2 is a laptop model Acer Asset id 4 is a laptop model Compaq Asset id 3 is a mobile phone number 555-060606 Asset id 5 is a mobile phone number 555-090909 |
Codice Sorgente
Il codice sorgente completo con tutti gli esempi di questo e del precedente articolo è scaricabile qui hibernate.