Monday, December 31, 2007

Hibernate: how to map a collection of embedded components keyed by one of the component's properties?

Quite often, during application development, I encounter the issue of mapping a collection of embedded components using Hibernate.

An embedded component is, in Hibernate, a user-defined value-typed class. It has no individual identity, hence the persistent component class requires no identifier property or identifier mapping; its lifespan is bounded by the lifespan of the owning entity instance.

When mapping a collection of embedded components, it is very important to override the equals() and hashCode() methods and compare all properties, because they are used by Hibernate to detect modifications to these components.

Very often, this collection of components also has a unique key property, that is, the collection should normally be implemented as a map indexed by the value of the key property.

The book "Java Persistence with Hibernate" and Hibernate's documentation illustrate 3 ways to map a collection of embedded components.

The first and highly recommended (by Hibernate) option is to map the collection to a set. This method requires all database columns mapped to the component class must be declared with not-null="true". It does not address the key property issue, either. I myself often find it unwieldy when dealing with the key property because it becomes my responsibility to enforce the map semantics. For example, when adding a new element, I need to iterate the set and find the existing element having the same key property value with the new element. If the existing element is found, depending on the business rule, I may throw an exception, or remove the existing element from the set and add the new element in. When you have several entity classes that have component map, you have to duplicate the same set iteration logic in many places...

The second option is to map the collection to an idbag. Again, it is the responsibility of my application to ensure the map semantics.

The third option is to map it to a map. Unfortunately, this option requires the removal of the key property from the component class. The following is the example extracted from the above-mentioned book to demonstrate the mapping of the images belonging to an item in the Caveate Emptor sample application, where the image name must be unique.

<map name="images"
table="ITEM_IMAGE"
order-by="IMAGENAME asc">

<key column="ITEM_ID"/>
<map-key type="string" column="IMAGENAME"/>
<composite-element class="Image">
<property name="filename" column="FILENAME" not-null="true"/>
<property name="sizeX" column="SIZEX"/>
<property name="sizeY" column="SIZEY"/>
</composite-element>
</map>

As can be seen from the mapping file snippet above, the "Image" component class no longer has a "name" property. This removal can be quite problematic. The key property is usually the most important property of a component class; removing this property from the component class and handle it merely as a map key not only potentially violates object oriented technology principles theoretically, but also can have significant consequences in practice. For example, the public interface of the entity class may need to be overhauled. Instead of an addImage(Image image) method, you need to provide an addImage(String imageName, Image image) method. Or, you have to create another value-type class just in order to wrap the name-deprived Image and the image name together.

Luckily, Hibernate 3.x provides a very powerful new feature called formula. This can easily solve our dilemma. It can map the component map to a map, but it does not require the removal of the key property from the component class. With formula, the above mapping can be modified to:

<map name="images"
table="ITEM_IMAGE"
order-by="IMAGENAME asc">

<key column="ITEM_ID"/>
<map-key type="string" column="IMAGENAME"/>
<composite-element class="Image">
<property name="name" type="string" formula="IMAGENAME"/>
<property name="filename" column="FILENAME" not-null="true"/>
<property name="sizeX" column="SIZEX"/>
<property name="sizeY" column="SIZEY"/>
</composite-element>
</map>

In short, it allows the "IMAGENAME" column to be mapped to both the map key and the key property of the "Image" class when loading from database. When persisting, only the map key is used to update the "IMAGENAME" column.

Now we can map a map of embedded components to a map without sacrificing the key property or writing messy codes to enforce map semantics...