【发布时间】:2020-09-08 16:31:02
【问题描述】:
在测试 JPA 乐观锁定时,我有简单的实体和相应的服务/控制器。锁定属性如下所示:
@Version
@Column(name = "opt_lock", nullable = false)
private short optLock;
测试场景。我有 1 个预先存在的记录。实体包含 ID、optLock 和 2 个数据字段 valueA 和 valueB。两者都设置为 1。 .save 方法调用上有断点,它被包裹在 try-catch 中,捕获所有 RuntimeExceptions。通过 TransactionTemplate 发起的整个事务也是如此。
在控制器上,我调用 PUT 方法将值 valueA 和 valueB 分别更新为 2-5。实体通过 ID 读取,仅更新 valueA 和 valueB 值,没有接触锁字段。挂在 .save 方法调用断点上,该断点被配置为只阻塞单线程。
再次执行相同的操作,但值为 200-1。线程也被阻塞了。
我取消暂停第二次更新,验证数据是否正确更新为 200-1,并在发出的语句中发送正确的 200-1 值,更新语句中使用的 optLock 值也是数据库中现有记录中存在的值。正确。
我取消暂停与第一次更新相关的线程,我看到,尝试使用正确的值 2-5 更新记录,使用了正确的 optLock,即。在此操作之前存在于 db 中的那个。此时db中不存在组合ID-optLock。因此无法更新此记录。但事实并非如此。没错!
但是……也不例外。为什么?
为了调试它,我尝试从项目中删除 net.ttddyy.datasource-proxy 如果它没有吞下它(它没有),我尝试如果我删除 @Version 注释整个场景开始失败(确实如此),我尝试通过hibernate代码调试,但我没有发现任何问题。
有什么建议会导致丢失异常吗?
更新:用普通的 EntityManager 替换了 spring 的东西,它是一样的。试图在 Intellij IDEA 之外运行它,事实证明,它有时可以用你的 db 调用做非常令人惊讶的事情,而且是一样的。更令人惊讶的是(对我来说)如果我引入 flush&clear 调用并在它们上放置断点,线程在刷新和清除后挂起以某种方式保持对持久性上下文或其他东西的锁定,但第二个线程只是挂起直到第一个线程完成,所以不能使用模拟锁定异常,因此我相信如果对持久性上下文的访问是同步的(可能是默认设置,这将是我当前的设置),我相信在现实生活中也不会引起问题。
【问题讨论】:
-
您是否在测试中提交事务?该错误只会在您提交第二个事务时发生。换句话说,当线程退出最外面的
@Transactional注解时。如果你想看看这个测试,我会抛出一个 OptimisticLockException:[github.com/augusto/jpa-workshop/blob/master/src/test/java/com/… -
是的,我确实提交了交易。我正在正常生产代码中“测试”这种行为,不涉及任何模拟或任何与测试相关的内容。在这些操作之后,数据被持久化到 oracle db 中。如前所述,如果我只是删除
@Version注释,两个更新将相互覆盖,并以“丢失更新”行为到达 db。使用@Version,它似乎可以工作,只是忽略会覆盖导致丢失更新行为的数据的提交。因此,从数据库内容的角度来看,它似乎有效,正确的数据在数据库中。也不例外。 -
我创建了最小的工作示例——它工作正常,异常被抛出。我只是使用忙等待而不是断点。我将该验证码移植回原始应用程序,它在那里不起作用。相同的依赖关系,相同的结构......只是不同的数据库和更多的代码和依赖关系。因此,由于代码是可以的,通过最小的工作示例验证,我需要更新原始应用程序并开始杀死代码和依赖项以找出谁在这样做。
-
查看我的回复。这是一个 oracle 驱动程序错误。