How do I know if a Grails model was changed since its retrieval?

北城以北 提交于 2020-01-14 14:43:08

问题


I want multiple users to be able to edit models in a web browser at the same time and conflicts detected when the save (so that the first user to write doesn't have their changes overwritten without the second user explicitly saying so). Here's the example:

User A retrieves object X and is looking at it in their browser.

Then User B retrieves object X, looks at it in their browser, modifies it, and saves it causing a post to the REST api in Grails and a save on the model to the database.

Then User A modifies and saves the object.

I want the application to detect that object X has been modified since user A retrieved it and I'll display a suitable message to user A with some options.

How do I know if the model has changed? Note that isDirty will not work and save(flush:true) will not work.

UPDATE

I see a couple answers that talk about Optimistic Locking and detecting a model change since it was submitted. I want to detect a change since it was retrieved by the user. Please read the question again.

Here I will clarify why Optimistic Locking will not solve the problem described above. However, I can imagine that there may be a way for me to use Optimistic Locking, but as it is described in the answers below and as it is described in the documentation, it will not help. Here's why.

Optimistic Locking works within the request, not across requests. If a second user updates an object while an update request is in progress from the first user, then optimistic locking will only allow one user to perform the update. Optimistic Locking protects against a read-modify-write being interleaved with another read-modify-write in the same request. Here's a time line (where time is top to bottom) of the events that optimistic locking protects against:

User 1                          User 2
presses submit                  presses submit
in update action
|   read model
|                               in update action
|                               |   read model
|                               |   modify model
|                               |   write model
|                               return
|   modify model
|   write model - FAILS
return error or something

Writing the model by the first users post fails because the optimistic locking check detects the record was modified since it was read.

What I would like to protect against is the following timeline:

User 1                          User 2
visits web app                  visits web app
clicks link to edit model       clicks link to edit model
in edit action
|   read model
|   render model
return
                                in edit action
                                |   read model
                                |   render model
                                return
user edits model                user edits model
user thinks... hmm...           user submits
                                in update action
                                |   read model
                                |   modify model from params
                                |   write model
                                return
user submits
in update action
|   read model
|   modify model from params
|   write model - OOPS! overwrote previous changes
return

From this example you can see that User 1 overwrites User 2's changes. But User 1 made her changes based on an old copy of the model from the database and perhaps would have done something different if she had seen User 2's changes that happened while she was thinking.


回答1:


Optimistic locking is built in, as dmahapatro has said. I would just add that Grails scaffolding takes this into account on the update actions for controllers. As an example, note that it checks the version to see if Book has already been updated since it was submitted.

def update(Long id, Long version) {
        def bookInstance = Book.get(id)
        if (!bookInstance) {
            flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), id])
            redirect(action: "list")
            return
        }

        if (version != null) {
            if (bookInstance.version > version) {
                bookInstance.errors.rejectValue("version", "default.optimistic.locking.failure",
                          [message(code: 'book.label', default: 'Book')] as Object[],
                          "Another user has updated this Book while you were editing")
                render(view: "edit", model: [bookInstance: bookInstance])
                return
            }
        }

        bookInstance.properties = params

        if (!bookInstance.save(flush: true)) {
            render(view: "edit", model: [bookInstance: bookInstance])
            return
        }

        flash.message = message(code: 'default.updated.message', args: [message(code: 'book.label', default: 'Book'), bookInstance.id])
        redirect(action: "show", id: bookInstance.id)
    }



回答2:


Why do you feel save(flush: true) won't work? Don't you want to use it or you think of something else?

You must have read about Optimistic Locking which works during save(flush: true).
version the domain class X so that the version gets updated on a successful save(with flush).

On dirty update, app would throw OptimisticLockingFailureException as shown:

def airport = Airport.get(10)
try {
    airport.name = "Heathrow"
    airport.save(flush: true)
}
catch (org.springframework.dao.OptimisticLockingFailureException e) {
    // deal with exception
}

If you do not want to handle the exception as shown above, then before editing the domain for User A, try to refresh it and then update the info, in order to get the latest version of Object X. However, this route is inadvisable for long running sessions.



来源:https://stackoverflow.com/questions/18861891/how-do-i-know-if-a-grails-model-was-changed-since-its-retrieval

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!