views:

530

answers:

5

Is there any generally accepted, proven-to-work way using hibernate, that keeps a history of an entitys changes in the database?

We need to keep track of quite some objects and we want to be able to undo changes to objects.

I tried using the interceptors of hibernate, but the old state of the object is only available when doing merge(). Mostly we do update() or saveOrUpdate() though...

I'm currently letting warp-persist together with Guice manage the session.

A: 

I've had exactly the same problem. One way round it is to lock() with the previous state and then merge() with the new state, rather than doing update() with just the new state.

Then hibernate is aware of the before/after and it is available in the interceptors/event listeners.

HTH.

Mike Q
So `saveOrUpdate()` gets indeed replaced by `merge()`?
kungfoo
I assume, because you are using update(), that you have detached instances. If so, do you have the object in the state it was before the changes were made to it? If so, you can use the above approach.I have a similar situation where I am shipping detached instances across to a remote GUI, which makes changes and sends them back. However I also have the GUI send back the "unchanged" state. Then for each update I do..session.lock( unchanged );session.merge( changed );
Mike Q
+2  A: 

I've done this with an Interceptor (creating my own based on EmptyInterceptor). I was able to get all changes (merge, save, saveOrUpdate and delete) in onFlushDirty().

The methods of EmptyInterceptor I used:

@Override
public boolean onFlushDirty(Object object, Serializable id,
    Object[] newValues, Object[] oldValues, String[] properties,
    Type[] types) throws CallbackException {

@Override
public boolean onSave(Object object, Serializable id, Object[] newValues,
    String[] properties, Type[] types) throws CallbackException {

@Override
public void onDelete(Object object, Serializable id, Object[] newValues,
    String[] properties, Type[] types) throws CallbackException {

In onFlushDirty() I had to query the previous state of the entity:

Connection c = sessionFactory.getCurrentSession().connection();
Session session = sessionFactory.openSession(c);

BaseEntity newBaseEntity = (BaseEntity) object;
BaseEntity oldBaseEntity = (BaseEntity) session.get(newBaseEntity.getClass(), newBaseEntity.getId());
Fried Hoeben
The `oldValues` parameter seems to be null, when `onFlushDirty()` is called whiel doing `update()` or `saveOrUpdate()`
kungfoo
I thought they were filled for me, but when I took a closer look at the code I saw that I was actually querying the current state of the entity from the session inside onFlushDirty(). So that's how I worked around the issue.
Fried Hoeben
+1  A: 
Bozho
+1  A: 

Hibernate Envers from JBoss is probably what you are looking for:

The Envers project aims to enable easy auditing/versioning of persistent classes. All that you have to do is annotate your persistent class or some of its properties, that you want to audit, with @Audited. For each audited entity, a table will be created, which will hold the history of changes made to the entity. You can then retrieve and query historical data without much effort.

Matej
+1  A: 

There is an automatic versioning and auditing project developed by JBoss: envers. I can recommend using this, I've implemented auditing by hand before and that can get really messy when you start approaching more complex usecases.

disown
I'll have a look at envers. I've stumbled across it before, but didn't remember it right now.
kungfoo

related questions