2 Optimistic Locking - Reference Documentation
Authors: Yasuharu NAKANO
Version: 0.4
2 Optimistic Locking
In order to handle optimistic locking, usewithOptimisticLock
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" istrue
.- When "version of modification base" < "current persistent version" is
false
orOptimisticLockingFailureException
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
}
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 themodificationBaseVersion
argument as follows:sampleDomain.withOptimisticLock { Object domain ->
// Here is always invoked regardless of the versions.
}
onConflict is optional
If you have nothing to do when a conflict occurs, you can omitonConflict
closure:sampleDomain.withOptimisticLock(modificationBaseVersion) { Object domain ->
// …
}
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) }
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 fromreturnValue
property.def result = sampleDomain.withOptimisticLock(modificationBaseVersion) { Object domain -> return "OK" }.onConflict { Object domain, Throwable caused -> return "NG" } assert result.returnValue == "OK"
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. }
Field Error Binding
By default, this plugin will bind a field error to an instance with a message codedefault.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 -> … }
modificationBaseVersion
is unnecessary, you can simply write it:sampleDomain.withOptimisticLock(errorBinding: false) { Object domain -> … }
sampleDomain.withOptimisticLock(errorBinding: false) { Object domain -> … }.onConflict { Object domain -> domain.errors.rejectValue("your.custom.code", [123] as Object[], "Default sample") }
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