org.hibernate.StaleObjectStateException:保存域类时

org.hibernate.StaleObjectStateException:保存域类时,hibernate,grails,gorm,grails-services,Hibernate,Grails,Gorm,Grails Services,我有以下代码 studentInstance.addToAttempts(studentQuizInstance) studentInstance.merge() studentInstance.save(flush:true) 它在上面代码的最后一行抛出以下异常 org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping

我有以下代码

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
,我还将服务的范围更改为request,但到目前为止没有任何帮助


这肯定是一个线程问题,因为只有20到30个用户同时调用此代码时才会发生这种情况。

这里的核心问题是关系是双向的,并且双方都被更改和版本化。调用
addToAttempts
时,由
hasMany
属性生成的
attempts
集合将初始化为新的空集合(如果为null),然后将实例添加到其中,实例的Student字段设置为拥有的Student,以确保内存中的状态与稍后从数据库重新加载所有内容时的状态相同。但是,当您启用了版本控制(乐观锁定)时,由于双方都发生了更改,所以都会得到版本凹凸。因此,如果两个并发用户之间的集合存在重叠,则会出现此错误。而且这是真实的-如果不显式锁定或使用乐观锁定,您可能会丢失以前的更新

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


解决方法不是分散在
合并
withTransaction
调用中,或是在这里或其他地方找到的其他随机内容,而是删除并发访问。在这里你很幸运,因为它完全是人造的。看到我不久前做的这个演讲了吗?遗憾的是,它与当前的Grails仍然像当时一样相关-我描述了删除集合并用更合理的方法替换它们的方法:

我也遇到过类似的情况,如果调用.merge()会发生什么在studentInstance上调用之前,在studentQuizInstance上?对于这种情况,有两个简单的调试技巧(认为这帮助我解决了一个类似的问题)-不,如果只是向联接表添加一个新记录,就不会有并发性问题。一个FK将指向共享尝试,另一个指向当前学生。其他并发插入将使用这些学生的ID。您可以在插件中看到一个将联接表显式映射为域类的示例-它使用这种方法映射用户和角色之间的多个。如果在
BuildConfig.groovy
plugins
部分添加
compile:spring安全核心:2.0-RC2“
(还包括
mavenRepo”http://repo.spring.io/milestone/“
存储库
块中),并运行
grails编译
,然后
grails s2 quickstart test User Role
您将看到UserRole.groovy域类。在我的例子中,它工作得非常好,直到系统进入高使用率。确切地说,并发问题就是这样。在你的开发环境中,以及在没有负载的情况下在prod中,一切都很好,因为你没有看到足够的流量来观察碰撞。