记录JPA并发save时遇到的坑

前言

在JPA中,使用save方法时是这样的:如果我们save的对象指定了主键,那么会根据主键先进行一次查询,如果查询记录不存在则执行insert语句,如果查询记录存在则执行update语句。

问题现象

业务场景是这样的:当某个用户钱包流水发生变化时,我们会先查询用户钱包是否存在(新注册的用户一开始没有钱包),如果存在则直接更新钱包余额,并添加一条流水,如果用户钱包不存在,则新生成钱包,再更新钱包余额,最后添加流水。

当同一个用户同时产生多条流水时,现在的流程则可能出现问题。

伪代码

// 查询用户钱包是否存在
User u = select(userId);
if(u == null){
	// 不存在则生成钱包,初始化钱包余额为0
	jpa.save(u);
}
// 添加流水
addFlow(u);
// 更新钱包余额
update(u);

问题分析

在这里插入图片描述

问题就发生在当第一个线程save成功后,第二个线程再执行时,save就会变成update,并且会覆盖第一个线程所执行的操作。

解决方法

只要让自定义save方法,就是insert操作就可以了,当第2个线程save时,会报主键冲突,然后第2个线程再重试一次,再次查询钱包是否存在时,就可以查询到了,然后就不用再生成钱包了,直接更新余额即可(更新余额幂等性)。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
如果使用 JPA 来访问数据库并执行更新金额的操作,则可以使用 JPA 的事务机制来保证并发安全性。在 JPA 中,您可以使用 `@Transactional` 注解来开启事务,并使用相应的方法来执行更新操作。 以下是一个使用 JPA 来更新金额并且保证并发安全的示例代码: ``` @Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Transactional public void updateUserBalance(int userId, double amountToDeduct) { // 获取用户 User user = userRepository.findById(userId).orElse(null); if (user == null) { throw new RuntimeException("User not found"); } // 更新余额 double newBalance = user.getBalance() - amountToDeduct; user.setBalance(newBalance); userRepository.save(user); } } ``` 在这个示例代码中,我们首先使用 `@Autowired` 注解来注入 `UserRepository` 对象。然后,我们使用 `@Transactional` 注解来开启事务,这样就可以保证在并发的情况下更新操作的原子性和一致性。接着,我们使用 `UserRepository` 对象来获取指定用户的信息,并更新用户的余额。最后,我们使用 `userRepository.save(user)` 方法将更新后的用户信息保存到数据库中。 如果在执行更新操作发生异常,事务将回滚以保证数据的一致性。否则,事务将提交以保存更新操作。使用 JPA 来更新金额并且保证并发安全可以让您更加方便地访问数据库并执行更新操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码拉松

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值