如何使用 JPA 和 Hibernate 修复 StaleObjectStateException

Posted

技术标签:

【中文标题】如何使用 JPA 和 Hibernate 修复 StaleObjectStateException【英文标题】:How to fix StaleObjectStateException with JPA and Hibernate 【发布时间】:2014-10-21 06:26:18 【问题描述】:

控制器逻辑:

def updateObject() 

    Object o = Object.get(params.id as Long)

    o.otherObjects.clear()

    objectDataService.saveObject(o.id)

    OtherObject newObject = new OtherObject;

    o.addToOtherObjects(newObject)

    objectDataService.saveObject(o.id)


服务逻辑

def saveObject(long profileId) 
    o.save(flush:true)

会发生什么

在 90% 的情况下,这将起作用。

问题

ERROR errors.GrailsExceptionResolver  - StaleObjectStateException occurred when processing request: [GET] /controller/updateObject - parameters:
stuff[]: data
Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.path.Object#1]. 
Stacktrace follows:
Message: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.path.Object#1]

我已经阅读了相关问题,并找到了您在上面看到的merge 电话。它解决了大约 50% 的案例,但不是全部。

【问题讨论】:

如果你把这个逻辑移到一个服务方法中,这样在同一个事务下完成,你会不会更好? 我不想让服务超载。你觉得有道理吗? 最好将业务逻辑转移到服务中并使您的控制器尽可能地精简(我所说的精简是指代码行数更少)。这将帮助您在需要时重用代码,并节省大量测试时间。 您是否期望许多用户对此操作并发请求,或者只有一个用户多次点击该操作? @NikhilBhandari 目前只是我自己测试。根本没有并发用户(都在本地主机上) 【参考方案1】:

StaleObjectStateException:

正如已经注释掉的有关此异常的内容。 当在刷新(显式或隐式)期间会话中存在无效的版本化域对象时,版本号会被碰撞,但不会持久保存到数据库中。然后,当它再次变为有效并保存并刷新时,hibernate 认为它是陈旧的,因为版本号与数据库中的版本不匹配,它会抛出一个 StaleObjectStateException。

这可以解决。

    更新前必须先找出版本值,如果是1,则必须使用程序更新。在特定的更新操作。 通过使用@version 注释。

关于@version:

乐观锁定的版本号机制是通过@Version 注解提供的。 示例:@Version 注释

@Entity
public class Flight implements Serializable 
...
    @Version
    @Column(name="OPTLOCK")
    public Integer getVersion()  ... 
 

这里,version 属性映射到 OPTLOCK 列,实体管理器使用它来检测冲突更新,并防止丢失将被 last-commit-wins 策略覆盖的更新@version

有关 Grails 的更多详细信息,请参阅下面的链接,它具有 Git 中心代码。test for GRAILS-8937: HibernateOptimisticLockingFailureException

【讨论】:

您能详细说明您的解决方案吗?我将如何使用@version 注释以及我将在第一个解决方案中更新什么? @SebastianFlückiger :这些是解决该异常的方法,请查看有关休眠框架的更多详细信息以了解版本使用情况。我还更新了有关版本的一些详细信息。其他给出的链接告诉 Grails 相关解决方案。1st阅读版本号的解决方案。从表中,然后通过编写代码更新该版本。【参考方案2】:

StaleObjectStateException 可以自然地出现在任何其他项目中。每次两个并发事务加载相同的实体版本并且每个事务都会更改该实体时,由于乐观锁定冲突失败,您最终会导致最后一个执行线程失败。

在您的情况下,在 JPA 边界之外有一个额外的调用可能会导致此问题:

Object o = PObject.lock(profileId)

每个事务都是线程绑定的,并且发生在一个 Session 中,因此两个竞争线程将为同一个实体 ID 保留两个 Object 引用。乐观锁定目标可以在没有任何其他显式锁定机制的情况下有效地工作。

如果您将修改后的实体引用发送到saveObject 版本,但您仍然收到此异常,这意味着您很有可能两个竞争线程修改相同的实体。

在这种情况下,pessimistic lock 将产生更好的结果,因为它涉及等待而不是 optimistic fail-fast approach。

【讨论】:

我更新了我的代码。 .lock() 调用不再存在。我仍然有大约 20% 的电话出现。它不是并发的(我作为单个用户单独制作它,所以没有并发编辑)【参考方案3】:

对我们来说,一些不同的方法最终解决了 StaleObjectException 定期发生的问题:

object = object.refresh()

在检索对象后刷新对象解决了我们的大部分 StaleObjectExceptions。尤其是在有人可能会从其他地方处理同一个对象并更改其某些成员的情况下(大多数问题来自集合成员)。

整体项目稳定性:

wrongly linked resources

我们在一个我们实际上并不需要的特定资源文件上有一个 404,因此有一段时间忽略了它。事实证明,丢失的文件会导致会话保持打开状态 - 从而左右制造 StaleObjects。

因此,作为对面临比平常更多的任何人的提示(某些 StaleObjects 可能总是发生 - 请参阅上面的答案) StaleObjectExceptions:确保 所有 资源正确链接并且您的开发者工具 (Chrome F12) 不会报告任何丢失的文件。

【讨论】:

以上是关于如何使用 JPA 和 Hibernate 修复 StaleObjectStateException的主要内容,如果未能解决你的问题,请参考以下文章

hibernate/JPA 中的 @PreUpdate 和 @Prepersist(使用会话)

如何使用 JPA 和 Hibernate 设置默认查询超时?

如何将继承策略与 JPA 注释和 Hibernate 混合使用?

如何使用 JPA 和 Hibernate 防止 SQL 注入?

Hibernate与Jpa的关系,以及使用分页和动态查询

如何使用 Hibernate 和 JPA 处理填充下拉列表?