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

Spring Boot JPA项目For循环内更新数据未提交、余额更新异常问题

问题根因

    1. @Transactional注解对私有方法不生效:Spring声明式事务基于AOP动态代理实现,只有public方法被类外部的调用者调用时,事务切面才会生效。你代码里的callToDeductBalanceAmount、callToUpdateBalanceAmount都是private方法,方法上的@Transactional完全不生效,导致每次Repository调用都单独开启短事务,更新完就提交,但JPA的持久化上下文(一级缓存)没有同步更新。
    1. JPQL更新语句不会自动同步一级缓存:你用自定义@Modifying注解的UPDATE语句直接操作数据库,执行后只会修改数据库的记录,不会更新JPA一级缓存里缓存的Wallet实体对象。第二次循环查询余额时,JPA直接返回了缓存中第一次查询到的余额为1000rs的旧实体,没有去数据库拉取最新值,所以第二次扣减是基于1000计算得到800rs,覆盖了第一次的更新结果。

修复方案

1. 修正事务配置

将需要事务控制的方法改为public,且避免同个类内部调用事务方法,确保AOP代理能切入事务逻辑。如果一定要在同类调用,需要通过代理对象调用,比如注入自身代理或者用AopContext.currentProxy()获取代理对象后再调用对应方法。

// 示例:修正方法修饰符
@Service
public class WalletService {
    @Override
    public String doSomething(List<RequestDTO> requestDTO) {
        // 调用public的事务方法,触发代理切入
        return callToDeductBalanceAmount(requestDTO);
    }

    // 改为public,事务生效
    @Transactional(rollbackFor = Exception.class)
    public String callToDeductBalanceAmount(List<RequestDTO> requestDTO) {
        // 原有逻辑不变
    }
}

2. 解决一级缓存不一致问题

两种方案二选一即可:

  • 方案一:放弃自定义UPDATE语句,改用JPA实体状态更新。查询到Wallet实体后直接修改余额属性,JPA会自动同步缓存和数据库,不需要手动写UPDATE语句:
for (RequestDTO lRequest : requestDTO)  {
    double requestAmount = lRequest.getRequestAmount();
    Wallet wallet = walletRepository.checkForAvailableBalance(...);
    if(null != wallet) {
        // 直接修改实体属性
        wallet.setBalanceAmount(wallet.getBalanceAmount() - requestAmount);
        // 保存后缓存自动同步,下次查询就是最新值
        walletRepository.save(wallet);
    }
}
  • 方案二:保留UPDATE语句,执行后手动清空一级缓存,让下一次查询强制走数据库拉取最新值。可以在Repository的UPDATE方法上加上clearAutomatically = true属性:
@Modifying(clearAutomatically = true)
@Transactional
@Query("UPDATE Wallet w SET w.balanceAmount = :updatedAmount WHERE ...")
Integer setUpdatedBalanceAmount(..., @Param("updatedAmount") double updatedAmount);

3. 补充并发控制(推荐)

当前逻辑没有并发控制,若同时有多个转账请求,仍会出现余额计算错误的问题。可以通过加悲观锁或乐观锁规避:

  • 悲观锁:查询余额时添加行锁,防止其他事务同时修改
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT w FROM Wallet w WHERE ...")
Wallet checkForAvailableBalance(...);
  • 乐观锁:给Wallet实体加@Version字段,更新时自动校验版本,冲突时抛出异常重试即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 12:06:03