GORM supports the registration of events as methods that get fired when certain events occurs such as deletes, inserts and updates. The following is a list of supported events:

To add an event simply register the relevant closure with your domain class.

Do not attempt to flush the session within an event (such as with obj.save(flush:true)). Since events are fired during flushing this will cause a StackOverflowError.

Event types

The beforeInsert event

Fired before an object is saved to the db

class Person {
   Date dateCreated

def beforeInsert() { dateCreated = new Date() } }

The beforeUpdate event

Fired before an existing object is updated

class Person {
   Date dateCreated
   Date lastUpdated

def beforeInsert() { dateCreated = new Date() } def beforeUpdate() { lastUpdated = new Date() } }

The beforeDelete event

Fired before an object is deleted.

class Person {
   String name
   Date dateCreated
   Date lastUpdated

def beforeDelete() { ActivityTrace.withNewSession { new ActivityTrace(eventName:"Person Deleted",data:name).save() } } }

Notice the usage of withNewSession method above. Since events are triggered whilst Hibernate is flushing using persistence methods like save() and delete() won't result in objects being saved unless you run your operations with a new Session.

Fortunately the withNewSession method allows you to share the same transactional JDBC connection even though you're using a different underlying Session.

The beforeValidate event

Fired before an object is validated.

class Person {
   String name

static constraints = { name size: 5..45 }

def beforeValidate() { name = name?.trim() } }

The beforeValidate method is run before any validators are run.

GORM supports an overloaded version of beforeValidate which accepts a List parameter which may include the names of the properties which are about to be validated. This version of beforeValidate will be called when the validate method has been invoked and passed a List of property names as an argument.

class Person {
   String name
   String town
   Integer age

static constraints = { name size: 5..45 age range: 4..99 }

def beforeValidate(List propertiesBeingValidated) { // do pre validation work based on propertiesBeingValidated } }

def p = new Person(name: 'Jacob Brown', age: 10) p.validate(['age', 'name'])

Note that when validate is triggered indirectly because of a call to the save method that the validate method is being invoked with no arguments, not a List that includes all of the property names.

Either or both versions of beforeValidate may be defined in a domain class. GORM will prefer the List version if a List is passed to validate but will fall back on the no-arg version if the List version does not exist. Likewise, GORM will prefer the no-arg version if no arguments are passed to validate but will fall back on the List version if the no-arg version does not exist. In that case, null is passed to beforeValidate.

The onLoad/beforeLoad event

Fired immediately before an object is loaded from the db:

class Person {
   String name
   Date dateCreated
   Date lastUpdated

def onLoad() { log.debug "Loading ${id}" } }

beforeLoad() is effectively a synonym for onLoad(), so only declare one or the other.

The afterLoad event

Fired immediately after an object is loaded from the db:

class Person {
   String name
   Date dateCreated
   Date lastUpdated

def afterLoad() { name = "I'm loaded" } }

Custom Event Listeners

You can also register event handler classes in an application's grails-app/conf/spring/resources.groovy or in the doWithSpring closure in a plugin descriptor by registering a Spring bean named hibernateEventListeners. This bean has one property, listenerMap which specifies the listeners to register for various Hibernate events.

The values of the map are instances of classes that implement one or more Hibernate listener interfaces. You can use one class that implements all of the required interfaces, or one concrete class per interface, or any combination. The valid map keys and corresponding interfaces are listed here:

NameInterface
auto-flushAutoFlushEventListener
mergeMergeEventListener
createPersistEventListener
create-onflushPersistEventListener
deleteDeleteEventListener
dirty-checkDirtyCheckEventListener
evictEvictEventListener
flushFlushEventListener
flush-entityFlushEntityEventListener
loadLoadEventListener
load-collectionInitializeCollectionEventListener
lockLockEventListener
refreshRefreshEventListener
replicateReplicateEventListener
save-updateSaveOrUpdateEventListener
saveSaveOrUpdateEventListener
updateSaveOrUpdateEventListener
pre-loadPreLoadEventListener
pre-updatePreUpdateEventListener
pre-deletePreDeleteEventListener
pre-insertPreInsertEventListener
pre-collection-recreatePreCollectionRecreateEventListener
pre-collection-removePreCollectionRemoveEventListener
pre-collection-updatePreCollectionUpdateEventListener
post-loadPostLoadEventListener
post-updatePostUpdateEventListener
post-deletePostDeleteEventListener
post-insertPostInsertEventListener
post-commit-updatePostUpdateEventListener
post-commit-deletePostDeleteEventListener
post-commit-insertPostInsertEventListener
post-collection-recreatePostCollectionRecreateEventListener
post-collection-removePostCollectionRemoveEventListener
post-collection-updatePostCollectionUpdateEventListener

For example, you could register a class AuditEventListener which implements PostInsertEventListener, PostUpdateEventListener, and PostDeleteEventListener using the following in an application:

beans = {

auditListener(AuditEventListener)

hibernateEventListeners(HibernateEventListeners) { listenerMap = ['post-insert':auditListener, 'post-update':auditListener, 'post-delete':auditListener] } }

or use this in a plugin:

def doWithSpring = {

auditListener(AuditEventListener)

hibernateEventListeners(HibernateEventListeners) { listenerMap = ['post-insert':auditListener, 'post-update':auditListener, 'post-delete':auditListener] } }

Automatic timestamping

The examples above demonstrated using events to update a lastUpdated and dateCreated property to keep track of updates to objects. However, this is actually not necessary. By merely defining a lastUpdated and dateCreated property these will be automatically updated for you by GORM.

If this is not the behaviour you want you can disable this feature with:

class Person {
   Date dateCreated
   Date lastUpdated
   static mapping = {
      autoTimestamp false
   }
}

If you put nullable: false constraints on either dateCreated or lastUpdated, your domain instances will fail validation - probably not what you want. Leave constraints off these properties unless you have disabled automatic timestamping.