Tuesday, June 23, 2009

How to provide hibernate persistence support along with class libraries

Suppose you want to encapsulate some kind of functionality in a class library, and you want to provide an easy way for the clients of your library to persist certain entities from your library with hibernate. I think the most straight forward way to do this, is to provide hibernate UserType implementations along with the entities in the class library that the client might want to persist. Joda Time for example, uses this approach. Joda Time is a class library to work with dates and times, and the Joda Time Hibernate Support project provides hibernate UserType implementations for many of the entities from Joda Time.

This approach works perfectly well, but it is limited to what one can express in a hibernate UserType. One of those limitations is that you can not (or almost not) use a single UserType to persist an entity that has a one2many relationship with something else (probably a value type). I encountered this problem when writing a class library to manage IP address pools (an IP address pool is a continuous range of IP addresses of which some are "free" and some are "allocated"). I represent an IP address pool as a class (IpAddressPool) which contains a sorted set of free sub-ranges in the pool. Persisting this would naturally result in a one2many mapping (using hibernate to map the SortedSet). Writing a single UserType which is capable of persisting IpAddressPool instances (including the one2many mapping) is (almost) not possible. I say almost, because it might be possible to access the JDBC connection directly and do everything by hand, but obviously I would like hibernate to manage the one2many relation.

So, when UserType implementations are not an option, what other options do we have?

  • we can simply do nothing and leave it up to the client to create the necessary hibernate mappings;
  • we can provide a mapping and a perhaps even a DAO and let the client use that by "injecting" its session factory into it;
  • we can do the same with annotations;
  • we can prevent the one2many mapping by serializing the sorted set into a single string representation or something like that, and use a simple UserType after all;
  • ... ?

The first solution is not really a solution at all. Apart from not helping the client of your library with the persistence aspect, it also means that we need to expose the internals of our classes such that clients can write correct hibernate mapping files for them.

The second solution is a bit better, but still not as good as the UserType approach. It typically results in having two mapping files (one from the class library, one from the client application) and thus complicates things like schema generation. It also makes it more cumbersome to integrate it with your client application. The provided DAO for example, might not be aligned with the DAO style of the client application, or the DAO might not provide certain queries the user wants to perform. Also, the client application might use annotations to express the hibernate mappings, while the class library uses an XML mapping file. Querying on properties of the entity in the class library is possible, but it means the internal representation has to be exposed. The UserType (and more specifically CompositeUserType) API has a nicer way to expose properties on which HQL queries can be performed without having to expose the internals of the entities in your class library, which can unfortunately not be used in this solution. It also becomes a bit problematic if you have multiple entities in your class library of which a client might only use a few.

The third solution is basically the same concept as the previous, except we could use annotations in stead of mapping files in the class library. It has all the typical advantages of annotations, but also means that the class library now needs a dependency on hibernate, even if the client doesn't care about persistence.

The fourth solution is more a workaround than a real solution. I think it might be appropriate in some situations though. In the IP address pool example this actually works very well, although it could become a problem for very large pools with lots of fragmentation (many entries in the sorted set of free sub-ranges) and it also complicates manual SQL database queries.
 
If anybody has better suggestions, or if anybody can prove me wrong on the claim that this is not feasible with a UserType, I'd be happy to hear about it.
 

1 comment:

anu said...

I know the details about the ip pool from this article. Thanks for this information. I normally use ip details to get the ip address lookup