(Quick Reference)

2 Optimistic Locking - Reference Documentation

Authors: Yasuharu NAKANO

Version: 0.4

2 Optimistic Locking

In order to handle optimistic locking, use withOptimisticLock method which is injected to domain class by the plugin.
  • withOptimisticLock method compares a version of modification base with current version.
    • withOptimisticLock's closure is invoked only when "version of modification base" < "current persistent version" is true.
  • When "version of modification base" < "current persistent version" is false or OptimisticLockingFailureException is catched,
    • it invokes onConflict closure if exists.
    • it sets a field error message to the domain instance.
  • When an exception except OptimisticLockingFailureException is thrown, it's not caught and just thrown up.

sampleDomain.withOptimisticLock(modificationBaseVersion) { Object domain ->

// Operations which might causes OptimisticLockingFailureException. // Here is invoked only when a version of modification base < current persistent version.

}.onConflict { Object domain, Throwable caused ->

// Operations to handle a failure of optimistic locking // e.g. to render edit page to re-input }

See also Optimistic and Pessimistic locking of Grails User Guide.

Type of modificationBaseVersion

modificationBaseVersion allows either Number (Long, Integer, etc.) or String. So you can pass a value of params in a controller.

Skip a version comparation

If a copmaration of version is unnecessary, you can omit the modificationBaseVersion argument as follows:

sampleDomain.withOptimisticLock { Object domain ->
    // Here is always invoked regardless of the versions.
}

In this case, the main closure will be always invoked.

onConflict is optional

If you have nothing to do when a conflict occurs, you can omit onConflict closure:

sampleDomain.withOptimisticLock(modificationBaseVersion) { Object domain ->
    // …
}

In this case, when something fails, it just only set error message to domain class.

Closure arguments

The first argument of the closure equals to delegate object. So you can use each one as you want.

sampleDomain.withOptimisticLock(modificationBaseVersion) { Object domain ->
    assert domain.is(sampleDomain)
}.onConflict { Object domain ->
    assert domain.is(sampleDomain)
}

All arguments of onConflict's closure are optional. So you can omit them. The followings are all valid.

sampleDomain.withOptimisticLock { /* … */ }.onConflict { Object domain, Throwable caused -> /* … */ }
sampleDomain.withOptimisticLock { /* … */ }.onConflict { Object domain -> /* … */ }
sampleDomain.withOptimisticLock { /* … */ }.onConflict { -> /* … */ }

Return value

If you want to use a return value of a closure, you can get it from returnValue property.

def result = sampleDomain.withOptimisticLock(modificationBaseVersion) { Object domain ->
    return "OK"
}.onConflict { Object domain, Throwable caused ->
    return "NG"
}
assert result.returnValue == "OK"

In case of a conflict, a return value of a onConflict's closure is returned.

assert result.returnValue == "NG"

Flushing session

In case of using persistence methods (e.g. save method), you must be flush a session.

def result = sampleDomain.withOptimisticLock(modificationBaseVersion) { Object domain ->
    domain.save(flush: true)
}.onConflict { Object domain, Throwable caused ->
    return "NG" // In case of error, you can catch and handle it here.
}

Otherwise, a conflict error occurs on flushing at outside of a controller action and you cannot handle the error. Instead, you will see the 500 error page if it's in development mode.

Field Error Binding

By default, this plugin will bind a field error to an instance with a message code default.optimistic.locking.failure. If you don't want it because i18n message is unnecessary or you want to use your custom message code, you can disable it by errorBinding parameter:

sampleDomain.withOptimisticLock(modificationBaseVersion, [errorBinding: false]) { Object domain ->
    …
}

If modificationBaseVersion is unnecessary, you can simply write it:

sampleDomain.withOptimisticLock(errorBinding: false) { Object domain ->
    …
}

If you want use your custom message code, you can use Spring's API directly like this:

sampleDomain.withOptimisticLock(errorBinding: false) { Object domain ->
    …
}.onConflict { Object domain ->
    domain.errors.rejectValue("your.custom.code", [123] as Object[], "Default sample")
}

The errorBinding parameter is true by default. So if you don't specify the parameter, the default field error will be bound. You can configure the default value in Config.groovy:

grails.plugins.domainlocking.defaultErrorBinding = false