Showing posts with label persistence. Show all posts
Showing posts with label persistence. Show all posts

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...

Tuesday, July 31, 2007

Handling referential integrity when doing persistence testing using DbUnit

DbUnit is a great tool when testing database persistence codes.

There are many ways to using DbUnit. Anyhow, my preferred way is to delete all records from the database tables and re-populate with test data before each test. For unit testing, I test against a memory database, such as Hypersonic; for integration testing, I test against a database that mirrors the one in production.

However, this approach sometimes will get you into the trouble of referential integrity violation.

For example, I had some integration tests against the PRODUCT table, and later I developd some integration tests against the ORDER table, which has a foreign key dependency on the PRODUCT table.

After checking in the latest changes, the continuous integration test failed because of data integrity violation. The cause was that the PRODUCT integration tests saved some records in both PRODUCT and ORDER table, with some orders referencing some products. When DbUnit tried to delete all records from the PRODUCT table, the database detected the violation.

How to solve this? It's pretty easy, just add an empty ORDER record to the test data XML file that is used to populate the database. Following is a sample of a FlatXmlDataSet file:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<product id="1" desc="abc" price="1.23"/>
<order/>
</dataset>
The trick is that in a FlatXmlDataSet file, the order of the tables defined must follow the dependency, that is, a later defined table can have a dependency on an earlier defined table. When DbUnit deletes records, it will delete records from the last defined table first.