0

我有以下代码

studentInstance.addToAttempts(studentQuizInstance)
studentInstance.merge()
studentInstance.save(flush:true)

并在上述代码的最后一行引发以下异常

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.easytha.Quiz#1]

我已经看到几个线程讨论相同的问题,据他们说,我也尝试过使用,我studentInstance.withTransaction还将studentInstance.withTransaction服务的范围更改为请求,但到目前为止没有任何帮助。

这绝对是一个线程问题,因为这只发生在 20 到 30 个用户同时调用此代码时。

4

1 回答 1

1

这里的核心问题是关系是双向的,双方都是变化和版本化的。调用时addToAttemptsattempts该属性生成的hasMany集合如果为null则初始化为一个新的空集合,然后将实例添加到其中,并将实例的Student字段设置为所属的student,以确保内存中的状态为如果您从数据库中重新加载所有内容,稍后将是相同的。但是,当您启用版本控制(乐观锁定)时,由于双方都发生了变化,因此双方都会遇到版本冲突。因此,如果您与两个并发用户之间的集合重叠,则会出现此错误。这是真实的——如果您没有明确锁定或使用乐观锁定,您将面临丢失先前更新的风险。

但这完全是人为的。这看起来像多对多,因此您只需在连接表中添加一个新行,该行指向学生和尝试。Grails 通过配置 Hibernate 检测变化的集合来做到这一点,但这实际上是利用了副作用。对于大型收藏品来说,它也非常昂贵。我在上面省略了关于调用的部分addToAttempts;如果那里已经有实例,则将从数据库中检索每个实例,即使您不需要它们。Grails 加载所有 N 个先前的元素(其中 N 可以是一个非常大的数字)并添加一个新的 N+1 st,所有这些都是为了让 Hibernate 可以检测到该新元素。你想要的只是插入一行,你最终会得到大量的数据库流量。

解决方法不是分散mergewithTransaction调用,或者您在此处或其他地方找到的其他随机内容 - 它是删除并发访问。在这里你很幸运,因为它完全是人造的。看看我不久前做的这个演讲,遗憾的是它仍然与当前的 Grails 一样相关 - 我描述了删除集合并用更明智的方法替换它们的方法:http: //www.infoq.com/presentations/GORM -表现

于 2014-01-22T15:46:32.467 回答