我有以下代码
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个用户同时调用此代码时。
答案 0 :(得分:1)
这里的核心问题是关系是双向的,并且双方都被更改和版本化。当您调用addToAttempts
时,attempts
属性生成的hasMany
集合初始化为新的空集合(如果它为null),然后将实例添加到其中,并且实例的Student字段为设置为拥有学生,以确保内存状态与稍后从数据库重新加载所有内容相同。但是当你启用版本控制(乐观锁定)时,由于双方都发生了变化,两者都会出现版本问题。因此,如果您与两个并发用户之间的集合重叠,则会出现此错误。这是真实的 - 如果您没有明确锁定或使用乐观锁定,您将面临丢失先前更新的风险。
但这完全是人为的。这看起来像是多对多的,所以你想要的只是在连接表中添加一个指向学生和尝试的新行。 Grails通过配置Hibernate检测到变化的集合来实现这一点,但这确实利用了副作用。对于大型系列来说,它也非常昂贵。关于拨打addToAttempts
的电话,我遗漏了上面的一部分;如果那里已经有实例,那么每个实例都会从数据库中检索出来,即使你不需要它们。 Grails加载所有N个前面的元素(其中N可以是一个非常大的数字)并添加一个新的N + 1 st ,所以Hibernate都可以检测到这个新元素。您只需要插入一行,最终会产生大量的数据库流量。
修复不是分散在merge
和withTransaction
调用,或者您在此处或其他地方发现的其他随机内容 - 它是要删除并发访问。在这里你很幸运,因为它完全是人为的。看到这个讲话我做了一段时间,遗憾的是仍然与当前的Grails一样 - 我描述了删除集合的方法,并用更明智的方法替换它们:http://www.infoq.com/presentations/GORM-Performance