Search code examples
hibernategrailsgrails-orm

StaleObjectStateException: Transaction Error in Grails


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?


Solution

  • 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