Hibernate version: 3.05
The following outlines a common development pattern that I think Hibernate could better support. So far I've hit brick walls attempting to make it work without resorting to compromising workarounds.
This is NOT intended to be any form of Hibernate bashing. In many respects Hibernate 3.0 is a perfect match for the framework I'm developing. I just think that the current inheritance features need some further refinement. If you disagree ... I'm all ears!
Persistence Requirements of an Extensible Component Framework:
* Framework API library employs Hibernate for object persistence.
- Core framework employs persistent POJO classes + Hibernate XML mapping files.
- Framework API auto creates database schema on startup using Hibernate DDL generation.
- Deployed as a single core framework JAR + extension JARs + deployment config file.
* Support for extension components (JARs) that contain persistent classes derived from abstract classes defined in the framework.
- An indefinite number of derived persistent extension classes (potentially hundreds) may be loaded
into a service at any given time.
- Each extension component defines its own Hibernate mappings (Mappings.hbm.xml).
Hibernate generates DB tables for component persisted classes on framework startup.
Deployment configuration file defines the set of components to load at startup.
Infrequent dynamic loading of components after startup is also supported (Rebuilds SessionFactory).
- By convention, each extension JARs contains a single package hierarchy based on a common root package name.
To avoid database table name collisions between components, persistent classes within a given
package hierarchy are mapped to tables with a common prefix using Hibernate DefaultNamingStrategy feature.
Deployment configuration file (external to the extension JARs) defines the unique table prefix for DB tables
used by each extension component.
- Standard HibernateUtil.java class replaced with a more "component friendly" alternative.
Hibernate Headaches:
Hibernate *nearly* supports this pattern but its inheritance mapping features appear to break in a few strategic places.
A key problem is that Hibernate appears to have been designed with the assumption that a base class will have a small and limited number of derived classes (low fanout). Unfortunately this is not the case in the framework pattern where an indefinitely large number of dynamically loaded derived classes may exist (high fanout). In this scenario, outer joins and unions over derived class tables become infeasible. At best they are slow (lots of joins or unions). At worst, they can fail to execute
because the database join (or union) count limits are exceeded within an SQL query.
The other main problem is that the <joined-subclass> tag does not offer some of the important features available in <join>. Putting <join> inside <subclass> creates a disjointed (no pun intended) coupling that does not cleanly support mixed inheritance mapping within a class hierarchy with more than 2 levels.
Inheritance mapping options:
What the framework pattern needs is support for "Table per subclass hierarchy" with a single table used for the base class and lazy loading of the subclass fields. It also requires table name generation for derived classes that uses .
Here's what Hibernate appears to offer:
Option 1: Table per class hierarchy
- Always employs outer joins so high derived class fanout is an issue. (?? Correct ??)
Slow at best, SQL breaks at worst.
- Won't support lazy loading of the derived class fields.
The core framework classes are normally only interested in base class fields but they are forced to load data from potentially 100's of derived component classes across 100's of tables.
*** Note: Adding the fetch="select" attribute to <joined-subclass> would fix the problem! ***
- No way to safely avoid name collisions between field names in derived classes since they are inside components that may be developed by different component authors.
Option 2: Table per subclass
- Employs outer joins to determine the derived class type.
Unacceptable performance cost or SQL failure with potentially 100's of derived classes that will map to 100's of tables.
- <joined-subclass> does not support fetch="select" that is required for lazy loading of the derived class.
Option 3: Table per subclass, using a discriminator and <join fetch="select">
Quote:
<class name="Base">
<id name="id" type="long">
<generator class="native"/>
</id>
<discriminator column="ComponentType" type="string"/>
<subclass name="DerivedA">
<join table="Component1_DerivedA" fetch="select">
...
</join>
</subclass>
</class>
- fetch="select" provides the needed lazy loading of derived class fields
- v3.05 does not generate DDL to create the joined table. (??Bug??)
- No support for table per subclass.
Instead you must use an other <join> inside each class derived from the component subclasses.
- Forced to specify the table name prefix (like "Component1_DerviedA" above) rather than have it be generated based on the class name and DefaultNamingStrategy interface.
- The table prefix must be specified at deployment time to avert collisions of table name prefixes.
Hence the table name cannot be coded into the mapping XML file.
- Invokes DefaultNamingStrategy.(String tableName) rather than using DefaultNamingStrategy.classToTableName(String classPath) that is needed by the framework pattern to generate the unique component specific table name prefixes.
- Component class *hierarchy* is forced to use a new <join> table for each inheritance level rather than use a single table for the component class hierarchy.
Option 4: Table per concrete class using <union-subclass>
- Same high fanout problems - You end up doing union across 100's of tables.
Option 5: Table per concrete class, using implicit polymorphism
- Same high fanout problems - You end up doing union across 100's of tables when the framework needs to query over the base class.
Workaround Hack
A workaround is a to avoid having a base class by instead using using an association relationship (rather than inheritance) between base class and derived component classes. A discriminator field is used but its is mamaged by the framework base class (rather than Hibernate) to select the "derived" class . If override methods are required, then they can be declared in an interface with matching delagate methods in the orginal base class.
This is clearly a compromise that results in the framework developer doing some of the mapping work that Hibernate is supposed to support.
[/list]