I have a TaskList entity with many workable Task entities and certain Tasks can "close" the TaskList.
class TaskList {
...
static hasMany = [
tasks: Task
]
}
class Task {
...
static belongsTo = [taskList: TaskList]
}
Now when a Task is updated and it can "close" the TaskList, I make a TaskListService
call to closeList()
class TaskListService {
def closeList(TaskList taskList) {
taskList.status = "CLOSED"
taskList.save()
}
}
// TaskController pseudo-update
def update () {
Task taskInstance = Task.get(params.id)
//... do something with the taskInstance
taskListService.closeList(taskInstance.taskList)
}
My problem is when a user updates the TaskList entity while the TaskListService
is updating it.
class TaskListController {
def update () {
TaskList taskListInstance = TaskList.get(params.id)
//... do some stuff
taskListInstance.properties = params
taskListInstance.save(flush:true)
}
}
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.giotta.TaskList#1]
How do I avoid this version conflict?
If you expect concurrent edits, you have to use explicit locking. You're using optimistic locking, which is so named since you use it when you hope that there won't be concurrent edits, and that in rare cases where this happens you can just handle the problem.
Use the lock()
method instead of the get()
method and do the update after that. Note that locking only makes sense in a transaction, so pass the id to the service instead of the whole instance.
class TaskListService {
def closeList(long taskListId) {
TaskList taskList = TaskList.lock(taskListId)
taskList.status = "CLOSED"
taskList.save()
}
}
// TaskController pseudo-update
def update () {
Task taskInstance = Task.get(params.id)
//... do something with the taskInstance
taskListService.closeList(taskInstance.taskListId)
}
Here I'm using the taskListId
dynamic property. You may need to change that to taskInstance.taskList.id