I've been trying to make GORM throw an optimistic locking error in an integration test. It has been said before that it is not possible to test for concurrent update errors without resorting to multiple threads, but even so I find the behaviour of my test case surprising:
void testOptimisticLocking() {
new Widget(foo:"bar").save(flush:true)
// Get the widget and change a property
def widget = Widget.findByFoo("bar")
assertNotNull widget
widget.foo = "baz"
def widgetVersion = widget.version
println "widget: $widgetVersion" // >>> widget: 0
// Discard the widget without saving; Hibernate now knows nothing about it
widget.discard()
// Get a different instance of the same widget from the database,
// with the old value for foo
def sameWidget = Widget.findByFoo("bar")
assertNotNull sameWidget
assertFalse System.identityHashCode(sameWidget) == System.identityHashCode(widget)
// Change the foo property and save
sameWidget.foo = "bat"
sameWidget.save(flush:true)
// Check the version has incremented
println "sameWidget: $sameWidget.version" // >>> sameWidget: 1
assertTrue widgetVersion < sameWidget.version
// Check the database hold the "bat" widget
sameWidget.discard()
assertEquals 0, Widget.countByFoo("bar")
assertEquals 1, Widget.countByFoo("bat")
// Reattach the original widget and try to save it
assertFalse widget.isAttached()
widget.attach()
println "widget: $widget.version" // >>> widget: 0
assertEquals widgetVersion, widget.version
assertEquals "baz", widget.foo
// TEST FAILS HERE
// No error is thrown, and the update fails silently!
shouldFail(org.hibernate.StaleStateException) {
assertTrue widget.validate()
widget.save(flush:true)
println widget.foo // >>> baz
widget.discard()
println "baz: " + Widget.countByFoo("baz") // >>> baz: 0
println "bat: " + Widget.countByFoo("bat") // >>> bat: 1
}
}
The re-attached instance of Widget is not persisted to the database, but no exception is thrown!
My test case is rather contrived, but still I am surprised by the result.
Can anybody explain this?