implements Elegance {

// Elwyn Malethan's musings on software development, mountain biking and general navel–gazing...

Articles tagged with 'jpa'

ActiveRecord timestamps using a Hibernate Interceptor

Having developed a lot of web applications of various sizes using both Ruby on Rails and Java-based web frameworks, I‘ve noticed a few differences. One is – for better or worse – that the frameworks in Rails tend to do a lot more for you out of the box.

One quite cool feature of ActiveRecord is it's implicit population of created_on and updated_on columns if they are present.

Well I decided that I‘d quite like to have equivalent columns for my articles on this website. Obviously since I‘m developing in Java I‘ll adopt the appropriate variable-name convention of updatedOn and createdOn, like so.

/* ... */
@Entity
public class BlogPost {

    /* ... */

    Date createdOn;
    Date updatedOn;

    /* ... */

    @Temporal(value = TemporalType.TIMESTAMP)
    public Date getCreatedOn() {
        return createdOn;
    }

    public void setCreatedOn(Date createdOn) {
        this.createdOn = createdOn;
    }

    @Temporal(value = TemporalType.TIMESTAMP)
    public Date getUpdatedOn() {
        return updatedOn;
    }

    public void setUpdatedOn(Date updatedOn) {
        this.updatedOn = updatedOn;
    }

    /* ... */
}

My main motivation for adding these fields is so I can show if an article has been modified since it was released or simply send the HTTP headers indicating the last time an article was edited, like so:

// e.g. Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT
getResponse().setHeader("Last-Modified",
                        new SimpleDateFormat("EEE, MMM yyyy HH:mm:ss z").format(blogPost.getUpdatedOn()));

Given the limited scope of a meager website such as mine, I could have easily just added these fields to my persistent class and added a filter to the update and create actions of my web application. Even simpler I could have added the code directly to the actions, though this would be less DRY. In either approach the changes would only apply to the the persistent entity representing the main articles of the site and I would have to repeat it when I add things such as comments. So I went for something more scalable.

Hibernate Interceptors

A couple of years ago, when I was with Beanlogic, I had an activity log requirement for customer. We had implemented a multi-user system and the administrators wanted to know about everything their users were up to. We were using Hibernate then and I'm using it for my site as well. What we used it to record an activity log then can also be used for my requirements here. That is an Interceptor.

Here‘s what my interceptor looks like:

package /* ...*/

public class TimestampInterceptor extends EmptyInterceptor {
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
                                Object[] previousState, String[] propertyNames,
                                Type[] types) {
        setValue(currentState, propertyNames, "updatedOn", new Date());
        return true;
    }

   public boolean onSave(Object entity, Serializable id, Object[] state,
                          String[] propertyNames, Type[] types) {
        setValue(state, propertyNames, "createdOn", new Date());
        setValue(state, propertyNames, "updatedOn", new Date());
        return true;
    }

    private void setValue(Object[] state, String[] propertyNames, String propertyToSet, Object value) {
        int index = Arrays.asList(propertyNames).indexOf(propertyToSet);
        if (index >= 0) {
            state[index] = value;
        }
    }
}

onFlushDirty deals with any updates to existing records and onSave deals with any new INSERTs of records. This means that if the fields are present in any of our persistent objects then they'll be populated. Even better, it will silently fail for any entities that don't have the timestamp columns. Note: onFlushDirty won't trigger if an update is performed where no properties have changed. So bear this in mind when testing.

When I first used this method for the activity log a drew a lot of inspiration from Using a Hibernate Interceptor To Set Audit Trail Properties, it was pretty useful and it goes into a little more detail. You may (will) notice significant similarities in implementation, particularly the setValue method.

First published on Nov 21, 2008. Last updated on: Dec 30, 2009.

 
People I like
Other sites