views:

54

answers:

3

I'm using straight Hibernate 3.0 without annotations.

When saving or updating domain objects, I would like to have Hibernate automatically generate the CREATE_DT and UPDATE_DT fields, as opposed to using database triggers.

What are the best practices for accomplishing this?

The background is that I have an object graph being passed from a client, that contains multiple objects. Some of which will end up being inserted and others updated. I could set the dates on the client, but this would be a bad idea. Setting the dates on the server means I would have to rifle through the graph and detect the changes.

It seems to me that Hibernate would have a facility for making this happen, but it is not jumping out at me.

+1  A: 

The Hibernate way to do this without using triggers would be to use Hibernate's event architecture and to register listeners for PreInsertEvent, PreUpdateEvent or SaveOrUpdateEvent (have a look at the org.hibernate.event package for a full list) to set and update the create/update dates.

Another option would be to use an interceptor, either Session-scoped or SessionFactory-scoped, to set the create and update dates in onSave(...) and the update date in onFlushDirty(...).

Maybe have a look at this previous answer for other options.

Pascal Thivent
A: 

The simplest way according to me to achieve this would be to have those fields as properties into your object class and set them privately through your constructor. When a property value would be changed, set your DateUpdated (for instance) to DateTime.Now (or whatever it may be in Java).

Once your entity is being persisted, it would automatically save those dates and persist them to your underlying database.

It is not directly with Hibernate, but I would consider this solution easier to implement than playing with Interceptors.

Hope this helps!

Will Marcouiller
So you pollute **all** your setters with calls to `setUpdateDate(...)`? I don't find this easier than an interceptor (that you set and you're done) and extremely ugly.
Pascal Thivent
It's no more than triggering some sort of an event, and just the like!
Will Marcouiller
What are you talking about? These events are there and they are triggered by Hibernate on your back. So there is nothing to add apart from the listener to leverage them. Maybe you should read the documentation.
Pascal Thivent
I wasn't talking about interceptors, but about what I suggested at first which you say it pollutes the setter. That is just like triggering an some sort of an event. About interceptors, I know. I just wanted to give an alternative. You know, some people just like when the result is what they expect, no matter what the code looks like. Now I know that the questionner mentionned Best Practice, but once he has all the alternatives, whether or not it is a best practice, he may choose. The main idea is allowing one to have his created and updated dates into the DB without DB trigger. That's all.
Will Marcouiller
and this "polluting" concept does just what was asked, no DB trigger.
Will Marcouiller
A: 

I'm checking Pascal's answer as the correct answer as he pointed me directly to the point in the documentation that provided detail and example code. But for the reader, I'm adding more detail here.

I tried both the hibernate events as well as the interceptors, and found that an interceptor worked better in my situation. It was relatively easy to implement.

Thanks for the help!

Below is the code for the interceptor:

public class AuditInterceptor extends EmptyInterceptor {

    private Log log = LogFactory.getLog(this.getClass());

    private int updates;
    private int creates;

    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state,
            String[] propertyNames, Type[] types) {
        if (entity instanceof AuditableVO) {
            creates++;
            // Find the create date and change it
            for (int i=0; i < propertyNames.length; i++) {
                if (propertyNames[i].equals("createDate")) {
                    state[i] = new Date();
                    return true;
                }
            }
        }
        return false;
    }
    @Override
    public boolean onFlushDirty(Object entity, Serializable id,
            Object[] currentState, Object[] previousState,
            String[] propertyNames, Type[] types) {
        if (entity instanceof AuditableVO) {
            updates++;
            // Find the update date and change it
            for (int i=0; i < propertyNames.length; i++) {
                if (propertyNames[i].equals("updateDate")) {
                    currentState[i] = new Date();
                    return true;
                }
            }           
        }
        return false;
    }
    @Override
    public void afterTransactionCompletion(Transaction tx) {
        if (tx.wasCommitted()) {
            log.info("Creations: " + creates + ", Updates: " + updates);
        }
        creates = 0;
        updates = 0; 

        super.afterTransactionCompletion(tx);
    }
}
Rydell