Wednesday, March 31, 2010

Hibernate and last modified date

A common practice is to put insert date and last modified date columns in a table. Hibernate/JPA provides a Version annotation which works nicely for managing the last modified date column. Managing create date is easily accomplished by initializing create date via the constructor.

Example:

import java.util.Date;

@Entity
@Table(name="audit_example")
public class AuditEntity {

@Id private long id;
private Date created;
@Version private Date lastModified;

public AuditEntity() {
created = new Date();
}

public long getId() { return id; }

public Date getCreated() { return created; }

public Date getLastModified() { return lastModified; }

public void setCreated(Date created) { this.created = created; }

public void setLastModified(Date date) { this.lastModified = date; }

}


In addition to autopopulating the "lastModified" property, @Version will enable optimistic locking. Anytime the AuditEntity is updated in the database, Hibernate will add an additional statement to the "where" clause of the update statement such as

update audity_entity
set ...
where id=:id and lastModified=:oldTimestamp

If another update has been committed in another session since the time the record was loaded in your session, the update on your session will throw a StaleObjectStateException. I don't have much experience with optmisitic locking in Hibernate, but that seems like a very slick way of detecting and preventing race conditions in systems where multiple users or processes could be updating the same record.

For those who want Hibernate to manage the last modified date column but don't want optimistic locking, another alternative is to use an event listener to set the lastModified property. I happened upon this solution before discovering the versioning solution. For this use case, I would use the versioning solution because its simple and I like optimistic locking. However the event listener solution can be customized to handle more complex use cases.

Here is a basic recipe for using event listeners to set the last modified date.

1. Create an interface called LastModifiable:

import java.util.Date;

public interface LastModifiable {

public void setLastModified(Date date);

}

2. Create a Listener that listens for the SaveOrUpdateEvent on Dateable entities and modifies the create and/or update properties:

import org.hibernate.event.SaveOrUpdateEvent;
import org.hibernate.event.def.DefaultSaveOrUpdateEventListener;

public class SaveOrUpdateDateListener extends DefaultSaveOrUpdateEventListener {

@Override
public void onSaveOrUpdate(SaveOrUpdateEvent event) {
if (event.getObject() instanceof LastModifiable) {
LastModifiable record = (LastModifiable) event.getObject();
audit.setLastModified(new Date());
}
super.onSaveOrUpdate(event);
}
}

3. Configure the above listener via hibernate.cfg.xml:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
.....
<event type="save-update">
<listener class="SaveOrUpdateDateListener"/>
</event>
</session-factory>
</hibernate-configuration>

Once these pieces are in place, any entity that implements the LastModifiable interface will automatically have its lastModified properties managed by hibernate.

Here is an example entity:

@Entity
@Table(name="audit_example")
public class AuditEntity implements LastModifiable {

@Id private long id;
private Date created;
private Date lastModified;

public AuditEntity() {
created = new Date();
}

public AuditEntity(long id) { this.id = id; }

public long getId() { return id; }

public Date getCreated() { return created; }

public Date getLastModified() { return updated; }

public void setCreated(Date date) { this.created = date; }

public void setLastModified(Date date) { this.lastModified = date; }

}

4 comments:

  1. This doesn't work for me. When I set this interface on a child object in a one to many relationship all childern get updated whenever they are loaded inside a trasaction. The problem is that this event listener fires and updates an object's property BEFORE the dirty check in the flush. Can you think of a better event to create the listener around? Or a way to make sure the object is dirty before you update it's last_modified? Works great for the created date!

    ReplyDelete
  2. Angus, you could check if the object is dirty by using event.getSession().isDirty(event.getObject()). I haven't had a chance to reproduce the problem you are seeing so this may not work as expected.

    ReplyDelete
  3. Setting created in the constructor is not a good solution. It records when the object is created, not when the row is inserted into the database. These two values could be an hour apart.

    ReplyDelete
  4. If you only want dity objects then use an Interceptor - they are specifically designed for altering obejcts:

    http://docs.jboss.org/hibernate/core/3.3/reference/en/html/events.html

    "The Interceptor interface provides callbacks from the session to the application, allowing the application to inspect and/or manipulate properties of a persistent object before it is saved, updated, deleted or loaded."

    Use onFlushDirty() for updates and onSave() for inserts.

    ReplyDelete