AppFuse: JPA Entity Inheritance
Posted by Martin Homik | Posted in Java, WebApp | Posted on 27-07-2007
0
Motivation. Originnaly, I picked up an approach from Semantic Web and RDF(s) where everything is a Thing and everthing else derives from that base class. In my current application, I wanted to have a base pojo class and other classes were supposed to extend that class. Since I am still new to this AppFuse and application frameworks, I had to get familiar with persistency and AppFuse in the context of entity inheritance following the try-and-error style. Here are my results.
Reading. Using google and folling discussions in AppFuse forums I collected a bunch of links. The most valuable are listed below:
Strategies. There are three strategies for entity inheritance: Single Table per Class Hierarchy, Table per Concrete Class, and Joined Subclass.
Single Table per Class Hirarchy. This is the default strategy. All classes in the hirerarchy are mapped to a single table in the database. If you have a deep hierarchy, you might end up having a table with many columns. With respect to memory resources this might be not the best choice, but it is runtime efficient and quite easy to program. To use this strategy, you have to apply the inheritance annotation in the root class. You might also want to stick to the default discriminator name (“DTYPE”) and discriminator type (String). Otherwise change it. Every class in the hierarchy should define a discriminator value which identifies the class in the table row. Here is how you code it.
-
-
@DiscriminatorValue("extend")
-
public class ExtendInheritEntitySingleTable extends BaseInheritEntitySingleTable {
-
-
private String extendedText;
-
-
…
To test whether the database schema has been set up properly type ‘mvn hibernate3:hbm2ddl’. If you want to insert values using dbunit, modify your sample-data.xml, like follows:
-
-
<!–BaseInheritEntitySingleTable-START–>
-
table name="single_table"><column>id</column>
-
<column>base_text</column>
-
<column>DTYPE</column>
-
<row>
-
<value description="id">1</value>
-
<value description="base_text">Base text 1</value>
-
<value description="DTYPE">base</value>
-
</row>
-
<row>
-
<value description="id">2</value>
-
<value description="base_text">Base text 2</value>
-
<value description="DTYPE">base</value>
-
</row>
-
<row>
-
<value description="id">3</value>
-
<value description="base_text">Base text 3</value>
-
<value description="DTYPE">base</value>
-
</row>
-
-
<!–BaseInheritEntitySingleTable-END–>
-
-
<!–ExtendInheritEntitySingleTable-START–>
-
<table name="single_table"><column>id</column>
-
<column>base_text</column>
-
<column>extended_text</column>
-
<column>DTYPE</column>
-
<row>
-
<value description="id">4</value>
-
<value description="base_text">E-Base text 1</value>
-
<value description="extended_text">Extended text 1</value>
-
<value description="DTYPE">extended</value>
-
</row>
-
<row>
-
<value description="id">5</value>
-
<value description="base_text">E-Base text 2</value>
-
<value description="extended_text">Extended text 2</value>
-
<value description="DTYPE">extended</value>
-
</row>
-
<row>
-
<value description="id">6</value>
-
<value description="base_text">E-Base text 3</value>
-
<value description="extended_text">Extended text 3</value>
-
<value description="DTYPE">extended</value>
-
</row></table>
-
<!–BaseInheritEntitySingleTable-START–>
Run ‘mvn test-compile’ to fill the tables with values from your sample-data.xml.
Table per Class. Using this strategy means that each class in the hierarchy is mapped to its own table in the database. It provides poor support for polymorphic relationships and usually requires SQL UNION queries. Hence, it is not recommended. However, here are the JPA annotations:
-
-
@Table(name = "extend_table_per_class")
-
public class ExtendInheritEntityTablePerClass extends BaseInheritEntityTablePerClass {
-
-
private String extendedText;
-
-
…
Run ‘mvn hibernate3:hbm2ddl’ to create the database schema.
-
-
<!–BaseInheritEntityTablePerClass-START–>
-
<table name="base_table_per_class"><column>id</column>
-
<column>base_text</column>>
-
<row>
-
<value description="id">1</value>
-
<value description="base_text">Base Text 1</value>
-
</row>
-
<row>
-
<value description="id">2</value>
-
<value description="base_text">Base Text 2</value>
-
</row>
-
<row>
-
<value description="id">3</value>
-
<value description="base_text">Base Text 3</value>
-
</row></table>
-
<!–BaseInheritEntityTablePerClass-END–>
-
<!–ExtendedInheritEntityTablePerClass-START–>
-
<table name="extend_table_per_class"><column>id</column>
-
<column>base_text</column>
-
<column>extended_text</column>
-
<row>
-
<value description="id">1</value>
-
<value description="base_text">E-Base Text 1</value>
-
<value description="extended_text">Extended Text 1</value>
-
</row>
-
<row>
-
<value description="id">2</value>
-
<value description="base_text">E-Base Text 2</value>
-
<value description="extended_text">Extended Text 2</value>
-
</row>
-
<row>
-
<value description="id">3</value>
-
<value description="base_text">E-Base Text 3</value>
-
<value description="extended_text">Extended Text 3</value>
-
</row></table>
-
<!–ExtendedInheritEntityTablePerClass-END–>
Run ‘mvn test-compile’ to populate your database schema with data from your sample-data.xml.
Joined Subclass Strategy. The root of the class hierarchy is represented by a single table, and each subclass has a separate table that only contains those fields specific to that subclass. The tables are joined using the same primary key names but the primary key of the extended class is also a foreign key to its superclass. In our case, it is ‘id’. This strategy provides good support for polymorphic relationships , but requires join operations to be performed when instantiating entity subclasses. This may result in poor performance for extensive class hierarchies.
-
-
@Table(name = "extended_joined_table")
-
public class ExtendInheritEntityJoined extends BaseInheritEntityJoined {
-
-
private String extendedText;
-
-
…
Create the database schema using ‘mvn hibernate3:hbm2ddl’. To populate the database with data add to your sample-data.xml:
-
-
<!–BaseInheritEntityJoined-START–>
-
<table name="base_joined_table"><column>id</column>
-
<column>base_text</column>
-
<row>
-
<value description="id">1</value>
-
<value description="base_text">Base text 1</value>
-
</row>
-
<row>
-
<value description="id">2</value>
-
<value description="base_text">Base text 2</value>
-
</row>
-
<row>
-
<value description="id">3</value>
-
<value description="base_text">Base text 3</value>
-
</row>
-
<row>
-
<value description="id">4</value>
-
<value description="base_text">E-Base text 1</value>
-
</row>
-
<row>
-
<value description="id">5</value>
-
<value description="base_text">E-Base text 2</value>
-
</row>
-
<row>
-
<value description="id">6</value>
-
<value description="base_text">E-Base text 3</value>
-
</row></table>
-
<!–BaseInheritEntityJoined-START–>
-
<!–ExtendInheritJoined-START–>
-
<table name="extended_joined_table"><column>id</column>
-
<column>extended_text</column>
-
<row>
-
<value description="id">4</value>
-
<value description="extended_text">Extended text 1</value>
-
</row>
-
<row>
-
<value description="id">5</value>
-
<value description="extended_text">Extended text 2</value>
-
</row>
-
<row>
-
<value description="id">6</value>
-
<value description="extended_text">Extended text 3</value>
-
</row></table>
-
<!–BaseInheritEntityJoined-START–>
Type ‘mvn test-compile’.
Tips.
- Delete the database schema whenever you are in a prototype try-and-error programming process. You’ll avoid a bunch of errors you might not be able to explain, because your changed code has not be propagated to the database schema or whatever. Starting with a clean database filters unnecessary discractions.
- Turn off foreign key validation as dbunit causes errors. It’s not the best step, but you will be able to compile and run your application. See here.
- Be aware that fields in sub classes are by default nullable. Otherwise, you have to define a default value. I am not sure whether this is the best solution, but I usually assign a default value right after declaration of the field.
Open questions.
- AppFuse seems to complain about a missing primary key when you derive an abstract class from BaseObject and and extend the abstract class by a concrete class. So far I have no explanation for this.
- Is it possible to overwrite the primary key entity?
- What about compund primary keys?
- So far, I have been working only at the maven-dbunit level. That is, I have not tested anything with DAOs and Managers. This could be the next step.
- I have not tested hierarchies deeper than one level.
Conclusion. In the process of going through this brief tutorial I relaised that Entity Inheritance might not be the best approach. I guess, I will stick to relationships as they provide me with more flexibility. There will be still a base class ‘Thing’, but other classes won’t derive from it. Saying this, the alternative would be to establish a OneToOne relationship between a class and its assumed “super” ‘Thing’ class. I wonder if this is different from the Joined Subclass Strategy?

