Hibernate Patterns: polymorphic many-to-one

Recently I found myself in a situation where I needed to define a persistent many-to-one relationship in which the concrete class of the many side could vary. In the past I'd had some issues with class inheritence and Hibernate, which can get sticky because of Hibernate proxies: you can't always cast the proxies to the desired type. Since I had to find a way to make this work, I decided to give interface inheritence a try. I started with a lightweight interface for a persistent object:

package example;

import java.io.Serializable;

/**
* Note that ID could be more generally defined as Serializable, but long worked best in this situation
*/
public interface PersistentLite extends Serializable
{
   long getId();

   void setId(long id);
}

The relationship is the classic parent-child relationship. This is our parent interface:

package example;

import java.util.Set;

/**
* In practice, the set of children should be restricted to instances of Child, but type parameters do not display well in HTML,
* so we use the raw type in this example.
*/
public interface Parent extends PersistentLite
{
   Set getChildren();

   void setChildren(Set children);
}

Here's the child. In this case, the child also has a couple of different implementations, so it is also defined as an interface:

package example;

public interface Child extends PersistentLite
{
   Parent getParent();

   void setParent(Parent parent);
}

Here are the mappings for parent and child:

<hibernate-mapping> 
   <class name="example.Parent" table="parent"> 
     <id name="id"> 
       <generator class="native"/> 
     </id> 
     <discriminator /> 
     <set name="children" cascade="all-delete-orphan" inverse="true"> 
       <key column="parent"/> 
       <one-to-many class="example.Child"/> 
     </set> 
   </class> 
</hibernate-mapping>

<hibernate-mapping> 
   <class name="example.Child" table="child"> 
     <id name="id"> 
       <generator class="native"/> 
     </id> 
     <discriminator /> 
     <many-to-one name="parent"/> 
   </class> 
</hibernate-mapping>

You would then add implementation classes and mappings, these are representative:

package example;

import java.util.*;

public class ParentImpl implements Parent
{
   private long id;
   private Set children = new TreeSet();
   private String extraData;

   public long getId()
   {
      return id;
   }

   public void setId(long n)
   {
      id = n;
   }

   public Set getChildren()
   {
      return children;
   }

   public void setChildren(Set set)
   {
      children = set == null ? new TreeSet() : set;
   }

   public String getExtraData()
   {
      return extraData;
   }

   public void setExtraData(String s)
   {
      extraData = s;
   }
}

package example;

public class ChildImpl implements Child
{
   private long id;
   private Parent parent;
   private String extraData;

   public long getId()
   {
      return id;
   }

   public void setId(long n)
   {
      id = n;
   }

   public Parent getParent()
   {
      return parent;
   }

   public void setParent(Parent p)
   {
      parent = p;
   }

   public String getExtraData()
   {
      return extraData;
   }

   public void setExtraData(String s)
   {
      extraData = s;
   }
}

<hibernate-mapping> 
   <subclass name="example.ParentImpl" extends="example.Parent"> 
     <property name="extraData"/> 
   </subclass> 
</hibernate>

<hibernate-mapping>
   <subclass name="example.ChildImpl" extends="example.Child">
     <property name="extraData"/>
   </subclass>
</hibernate-mapping>

The key insight is this: because the interfaces are mapped, Hibernate generates proxies that implement the interface, which eliminates a lot of potential casting problems. By contrast, if you use class inheritance, Hibernate can only generate proxies that implement the interface which the class exposes to Hibernate, and is not necessarily the same as the interface exposed to the system at large. This causes confusion. Moreover, since a lot of casting issues can occur from inside the subclass, via private methods and data, using an interface eliminates a lot potential problems because the interface is well-defined. You could still run into issues when casting, but they are greatly reduced if the interface is properly defined. Finally, using a lightweight interface as the basis of the relationship gives the implementation the flexibility to inherit from a richer base class if desired.

jed

Jed Prentice is a senior software engineer who has been developing business applications in a variety of industries long enough to have seen a lot of trends come and go. He is an expert in the field of object-oriented software development specializing in distributed systems and web applications written in Java and the practice of agile development methodologies.


Perhaps I’m simply too green when it comes to Hibernate, but… Is your Hibernate XML above intended to be complete or are you leaving out details for the sake of clarity?

I have a very practical reason for asking, because I find myself needing to implement something similar to what you discuss above. I have worked with table-per-hierarchy before and with associations, but have never bumped into my current issue:

“There is a many-to-one relationship between WidgetTable and WidgetInfoTable. WidgetTable needs to map to a number of concrete subclasses of a common interface, but WidgetInfoTable contains the discriminator column needed to determine which subclass.”

At any rate, if you could do a public service to the dimwitted by posting a complete example of the Hibernate XML which meets your needs, that might get my oxcart out of the ditch.

—Ken

The mapping is complete. The empty discriminator tag simply uses a column named class to store the fully-qualified class name. You can specify various attributes to control these values if you wish, but I have been happy with the defaults.

As far as your issue is concerned, Hibernate figures out which classes to instantiate based on the discriminator column. You don’t have to do anything special assuming you understand how Hibernate deals with polymorphism, but that is another subject and is covered in the manual. If you have specific questions, let me know.