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

如何正确更新含@Version字段的实体?乐观锁异常未触发

乐观锁不触发的原因及可靠更新方案

嘿,我来帮你拆解下问题所在,以及怎么实现可靠的乐观锁更新:

你的乐观锁完全没触发,核心是两个关键错误:

  1. 手动覆盖了实体的version字段:你在更新时直接把DTO里的version赋值给了tenant实体——这相当于绕开了JPA的乐观锁逻辑。JPA的乐观锁是依赖加载实体时的原始version值和数据库里的值做比对,你手动替换后,save时会用这个新设置的值去和数据库匹配,自然不会触发异常。
  2. 缺少事务上下文:没有显式声明事务的话,findById和save是两个独立的持久化操作。findById加载实体后,实体就脱离了持久化上下文,save的时候JPA会把它当成detached实体处理,直接生成新的version覆盖数据库值,根本不会做乐观锁校验。

正确的实现方式

1. 删掉手动设置version的代码

DTO里的version应该用来做前置校验,而不是直接赋值给实体。我们要先比对DTO的version和数据库加载的实体version是否一致,不一致直接抛出异常:

Option<Tenant> updateTenant(Long id, UpdateTenantDTO dto) {
    return repository.findById(id)
        .map(tenant -> {
            // 先校验版本是否匹配
            if (!tenant.getVersion().equals(dto.getVersion())) {
                throw new OptimisticLockingFailureException("该租户信息已被其他进程修改,请刷新后重试");
            }
            // 只更新需要修改的字段
            if (isNotEmpty(dto.getName())) {
                tenant.setName(dto.getName());
            }
            return tenant;
        })
        .map(repository::save);
}

2. 添加事务注解

在方法上加上@Transactional,确保findById和save处于同一个持久化上下文内。这样JPA会在事务提交时,自动用加载实体时的原始version和数据库中的值做比对,不一致就抛出乐观锁异常:

import org.springframework.transaction.annotation.Transactional;

@Transactional
Option<Tenant> updateTenant(Long id, UpdateTenantDTO dto) {
    return repository.findById(id)
        .map(tenant -> {
            if (!tenant.getVersion().equals(dto.getVersion())) {
                throw new OptimisticLockingFailureException("该租户信息已被其他进程修改,请刷新后重试");
            }
            if (isNotEmpty(dto.getName())) {
                tenant.setName(dto.getName());
            }
            return tenant;
        })
        .map(repository::save);
}

3. 依赖Spring Data JPA的自动乐观锁处理

如果你用的是Spring Data JPA的JpaRepository,save方法默认会处理乐观锁逻辑。当实体处于managed状态(事务内加载的实体就是managed),save时会生成这样的SQL:

UPDATE tenant SET name = ?, version = ? WHERE id = ? AND version = ?

如果这条语句影响行数为0,说明数据库里的version已经变了,JPA就会自动抛出OptimisticLockingFailureException,这正是我们要的乐观锁异常。

额外提醒

  • 不要手动修改实体的version字段(除了初始化):你的Tenant类里version = Instant.now()初始化是没问题的,但更新时绝对不能碰这个值。
  • 捕获乐观锁异常后要给用户友好提示:比如前端可以提示“数据已被修改,请刷新后重试”,后端可以统一处理这个异常返回对应的HTTP状态码(比如409 Conflict)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:01:52