Full source code is provided in the package: de.laliluna.inheritance.union for the XML mapping and de.laliluna.inheritance.tableperclass for the annotation mapping. The class hierarchy does not differ from our former examples. The difference is in the tables. Parent class objects will be saved in a parent class table and each sub class objects in a separate table.
An instance of Mouse is saved in table mouse, a KitchenMouse in the kitchen_mouse table and an instance of LibraryMouse in the library_mouse table.
Imagine a relation from the class House. The relation is persisted in a join table house_mouse containing two columns house_fk and mouse_fk. The house_fk references the house table and mouse_fk one entry either in the table mouse, kitchen_mouse or library_mouse. As the table is not known before hand, we cannot impose a foreign key reference constraint.
A further limitation exists with id generators. The primary key of mouse, kitchen_mouse or library_mouse must be shared across all these tables. You cannot use IDENTITY or AUTO as strategy to generate primary keys. Choose a shared SEQUENCE if supported by your database, or another strategy that guaranties unique primary keys across these tables. The parent class can be abstract.
Annotation mapping.
import javax.persistence.Entity; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; ...... snip .... @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class Mouse { @Id @GeneratedValue private Integer id; private String name; .......
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) defines the inheritance strategy. The sub classes are quite simple and do not contain inheritance specific annotations.
@Entity public class KitchenMouse extends Mouse{ private String favouriteCheese; ......
@Entity public class LibraryMouse extends Mouse{ private String favouriteBook; .....
The class House contains a normal one to many relation to Mouse. In this case a relation table is used but you could use a simple foreign key column in the mouse, kitchen_mouse and library_mouse tables as well.
import java.util.HashSet; import java.util.Set; import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.OneToMany; ..... snip ........ @Entity public class House { @Id @GeneratedValue private Integer id; @OneToMany @JoinTable(name = "house_mouse", joinColumns = @JoinColumn(name = "house_fk"), inverseJoinColumns = @JoinColumn(name = "mouse_fk")) private Set<Mouse> mice = new HashSet<Mouse>();
XML mapping
We use the mapping union-subclass.
<hibernate-mapping package="de.laliluna.inheritance.union"> <class name="House"> ....... snip ..... <set name="mice" table="house_mouse"> <key column="house_fk"></key> <many-to-many class="Mouse"> <column name="mouse_fk"></column> </many-to-many> </set> </class> <class name="Mouse" table="mouse"> ....... snip ..... <union-subclass name="KitchenMouse" table="kitchen_mouse"> <property name="favouriteCheese"/> </union-subclass> <union-subclass name="LibraryMouse" table="library_mouse"> <property name="favouriteBook" /> </union-subclass> </class> </hibernate-mapping>
Union subclass mapping does build a union from the parent class and all subclasses. Columns that do not exist in a subclass are set to null. This has no influence on your classes but is needed to make union work. If your Mouse class is abstract or if you do not need a table mouse you can replace
<class name="Mouse" table="mouse">
with
<class name="Mouse" abstract="true">
Now, let’s have a look at the behaviour of this mapping. A query like
session.createQuery("from Mouse m where type(m) = LibraryMouse").list();
will create a select from a subselect, where the subselect does union all the subclasses and the parent class table.
select mouse0_.id as id5_, mouse0_.name as name5_, mouse0_.favouriteCheese as favourit1_6_, mouse0_.favouriteBook as favourit1_7_, mouse0_.clazz_ as clazz_ from ( select id, name, null::varchar as favouriteCheese, null::varchar as favouriteBook, 0 as clazz_ from Mouse union all select id, name, favouriteCheese, null::varchar as favouriteBook, 1 as clazz_ from KitchenMouse union all select id, name, null::varchar as favouriteCheese, favouriteBook, 2 as clazz_ from LibraryMouse ) mouse0_ where clazz_=2
To build a union like this takes some time. An advantage of the union approach is that an insert of a class Mouse will only happen in its table. The parent table is not touched.
Samples of use.
/* create and set relation */ House house = new House(); Mouse bea = new Mouse("Bea"); house.getMice().add(bea); KitchenMouse john = new KitchenMouse("John"); house.getMice().add(john); LibraryMouse tim = new LibraryMouse("Tim"); house.getMice().add(tim); session.save(bea); session.save(john); session.save(tim); session.save(house); /* get all kind of mice*/ List<Mouse> result = session.createQuery("select m from Mouse m").list(); /* select all kitchen mice who like Gauda cheese blue flowers */ List<KitchenMouse result = session .createQuery("select m from KitchenMouse m where m.favouriteCheese ='Gauda'").list(); /* select all mice of type LibraryMouse */ List<LibraryMouse> result = session .createQuery("select m from Mouse m where type(m) = LibraryMouse ").list();