Tuesday, 1 May 2007

Versioning of database entities with Hibernate

At work I had the requirement to introduce a versioning mechanism for database entities in an already productive application which used Hibernate as persistence framework.

Versioning was meant in the following way: the state of an entity had to be reconstructable at any point of time and for each change the id of the changer and the time had to be stored. In short: who made what when?

The following ideas and techniques are not new and are not my own ideas. The implementation orients itself on suggestions from the book "Hibernate in Action" written by Gavin King and Christian Bauer, which I can recommend.

The database in use did not have triggers, therefore an implementation using database triggers was not possible.
Instead of that the mechanism was to be implemented with Hibernate means.

Since the application was already in production the changes had to be minor.
One possibility to achieve the goal would have been to introduce a superclass for all entities to be versioned. But this would have been a bigger change. The risk of errors would have been to great.

The implementation was done in the following way:
1. a new annotation Historicizable marks the entities to be versioned
2. two new database tables were introduced: EntityHistory and EntityHistoryData. The former includes class name and id of the changed entity, date of the change, the initiator of the change and the type of change (INSERT, UPDATE, DELETE). The latter contains information about each changed attribute of the entity. There is a one-to-many relationship between them.
3. an Interceptor was defined which collects data about changes and stores them.

The Interceptor
gets registered at the SessionFactory. It is therefore used in each Hibernate session. Our interceptor implements the methods onSave, onFlushDirty, onDelete, afterTransactionCompletion and postFlush. The first three just collect information about changes and create a list of EntityHistory beans. The interceptor checks whether the entity for which the interceptor was called is annotated with Historicizable. If not no data is collected.
afterTransactionCompletion checks whether the current transaction was rolled back. In this case the list of collected EntityHistory beans is cleared.
Method postFlush makes the list of EntityHistory beans persistent. This method has to use its own Hibernate Session but which can share the same connection.

The existing API method of the application did not have an argument which defines the initiator of a change. Instead of changing each of these methods the initiator data can be set as a ThreadLocal variable on the interceptor. So the versioning mechanism relies on the client of the application API to set the initiator properly before calling any API method which could possibly include versioning.

E.g. some API methods were published as webservices. The webservices itself were extended so that initiator data could be send as a SOAP header with the request.

This versioning mechanism only works for entities having only primitive or simple properties. Versioning of entities with references to other entities is not supported.

2 comments:

Anonymous said...

I think they stole your tag and made something called @Versioned :)
http://www.theserverside.com/news/thread.tss?thread_id=49190

Die Preetzer Abelings said...

Damn!
I forgot to refine the idea and make this a real product.

Envers finalized the idea with support for automatic versioning of embedded components and relations.
And they deliver an api to recover old versions as I understood.

Sounds very interesting though I did not test it.