You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot JPA中@Version与@Transactional联用失效问题

为什么@Transactional会导致@Version乐观锁逻辑异常?

核心原因:持久化上下文的快照机制

当服务方法被@Transactional修饰时,整个方法处于同一个JPA持久化上下文(EntityManager)中,Hibernate的托管实体机制会覆盖你手动设置的版本号:

  1. 调用rep.findById(id).get()时,实体被加载到持久化上下文,进入托管状态。Hibernate会自动为该实体创建一份快照,记录加载时的所有属性值,包括数据库中当前有效的rowVersion。
  2. 你手动调用entity.setRowVersion(rowVersion)修改版本号,只是改变了实体对象的当前状态,但Hibernate的快照中仍然保留着加载时的正确版本号。
  3. 事务提交阶段,Hibernate执行脏检查并生成更新SQL时,会忽略你手动设置的@Version字段值,而是使用快照中的原始版本号去和数据库中的版本号做乐观锁比对。这就导致即使你传入错误的版本号,也不会触发并发冲突——因为Hibernate根本没用到你设置的值。

正确的乐观锁实现方式

要实现预期的并发控制逻辑,你需要在加载实体后手动验证版本号,而不是直接修改实体的rowVersion:

@Service
@Transactional
public class SomeService {
    private final SomeEntityRepository rep;

    public void update(Long id, Long rowVersion, String value) {
        var entity = rep.findById(id).orElseThrow(() -> new RuntimeException("实体不存在"));
        // 手动验证传入的版本号与数据库当前版本是否一致
        if (!Objects.equals(entity.getRowVersion(), rowVersion)) {
            throw new OptimisticLockingFailureException("实体已被其他用户修改");
        }
        entity.setValue(value);
        // 无需手动设置rowVersion,Hibernate会自动递增版本号
        rep.save(entity);
    }
}

补充说明:非事务场景的差异

如果没有@Transactional,调用rep.findById(id).get()后,实体加载完成就会脱离持久化上下文(进入分离状态)。此时你手动设置rowVersion再调用save,Hibernate会将其视为分离实体的更新,这时候才会使用你设置的版本号去做乐观锁检查——但这种场景下每次操作都会开启新的持久化上下文,效率较低,也不符合事务管理的最佳实践。

内容的提问来源于stack exchange,提问作者aweis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 17:53:31