【问题标题】:How do I know if a Grails model was changed since its retrieval?我如何知道 Grails 模型自检索后是否已更改?
【发布时间】:2013-09-22 14:27:32
【问题描述】:

我希望多个用户能够同时在网络浏览器中编辑模型,并在保存时检测到冲突(这样第一个写入的用户不会在没有第二个用户明确表示的情况下覆盖他们的更改) .示例如下:

用户 A 检索对象 X 并在浏览器中查看它。

然后用户 B 检索对象 X,在他们的浏览器中查看它,对其进行修改,然后将其保存,从而将其发布到 Grails 中的 REST api,并将模型上的 save 发送到数据库。

然后用户A修改并保存对象。

我希望应用程序检测到对象 X 在用户 A 检索到对象 X 后已被修改,我将向用户 A 显示一条合适的消息,并提供一些选项。

我如何知道模型是否已更改?请注意,isDirty 将不起作用,save(flush:true) 将不起作用。

更新

我看到一些关于乐观锁定和检测模型更改的答案,因为它已提交。我想检测更改,因为它已被用户检索。请再次阅读问题。

这里我将澄清为什么乐观锁定不能解决上述问题。但是,我可以想象我可能有一种方法可以使用乐观锁定,但是正如下面的答案和文档中所描述的那样,它无济于事。原因如下。

乐观锁定在请求中起作用,而不是跨请求。如果第二个用户在第一个用户的更新请求正在进行时更新了一个对象,那么乐观锁定将只允许一个用户执行更新。乐观锁定可防止在 same 请求中读取-修改-写入与另一个读取-修改-写入交错。以下是乐观锁定所防止的事件的时间线(时间从上到下):

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

第一个用户发布的模型写入失败,因为乐观锁定检查检测到记录在读取后被修改。

我想要防止的是以下时间表:

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

从这个示例中,您可以看到用户 1 覆盖了用户 2 的更改。但是用户 1 根据数据库中模型的旧副本进行了更改,如果她在思考时看到用户 2 的更改,她可能会做一些不同的事情。

【问题讨论】:

    标签: hibernate grails grails-orm


    【解决方案1】:

    为什么你觉得save(flush: true) 不起作用?你不想用还是想别的?

    您必须阅读过有关save(flush: true) 期间工作的Optimistic Locking
    version 域类 X 以便在成功保存(使用刷新)时更新版本。

    在脏更新时,应用会抛出OptimisticLockingFailureException,如图所示:

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

    如果您不想处理如上所示的异常,则在编辑用户 A 的域之前,尝试refresh 它然后更新信息,以获得最新版本的对象 X。但是,这条路线不适合长时间运行的会话。

    【讨论】:

    • 乐观锁定只检测请求中的冲突。我正在寻找跨请求检测冲突。请参阅我更新的问题。
    • 再次访问乐观锁定。每次休眠更新数据库中的行(刷新时)时,它都会工作并更新版本。用户在更新之前或之后是否在思考、唱歌或冬眠并不重要。 :-) @Jason
    【解决方案2】:

    正如 dmahapatro 所说,内置乐观锁定。我只想补充一点,Grails 脚手架在控制器的更新操作中考虑了这一点。例如,请注意它会检查版本以查看 Book 自提交后是否已更新。

    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)
        }
    

    【讨论】:

    • 这个。您必须自己检查版本更改。它应该内置到所有具有 IMO 版本的对象中。
    • update 方法在哪里调用? version 参数集在哪里?如果在请求开始时从数据库中设置它,那么这将不起作用。如果它是从用户提交的对象中设置的,并且具有用户最初请求开始编辑对象时的值,那么这应该可以工作。
    • version 位于 edit.gsp 的隐藏字段中(或任何您可能称呼它的名称)。因此,当提交表单时,它会在请求中发送该版本号。然后将该版本与当前数据库的版本进行比较,以确定它们是否相同。没有“这应该有效”。它确实有效。
    • @Gregg,如果我正在实现 Rest 端点而不使用 HTML 表单,并且我坚持将“版本”属性作为请求正文的一部分而不是 URL 的一部分,该怎么办?我发现“bookInstance.properties=params”不会复制版本属性,“bindData(dst, params)”也不会这样做。如何从请求正文中检索版本属性,然后再处理请求正文的 InputStream 以绑定我的其他属性? InputStream 只能读取一次。此外,就 Jason 而言,我认为此版本检查是内置的,无需手动编写代码。
    • def 版本 = params.version。这也发生在瞬态属性上。它们不会自动绑定。从请求参数中提取版本后,您可以对其进行任何操作以进行比较。
    猜你喜欢
    • 1970-01-01
    • 2017-03-23
    • 2021-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 2015-03-28
    相关资源
    最近更新 更多