SpringBoot事务失效之this
@Transactional
注解的逻辑是通过动态代理来实现的,而生成这个动态代理类分成了两步:
1、向spring容器注册事务相关的切面逻辑
2、根据切面逻辑生成动态代理
此时牢记第二部,是生成动态代理去帮助我们实现事务,那么在什么场景下,导致我们使用事务时,失效呢
很显然,在我们方法中调用事务方法时会导致失效,这就是因为方法调用方法,其指向是this,例如:
我们可以看到,在createOrder方法中,调用了事务方法 insertOrderAndReduceStock()方法,那么这个场景就是刚刚所说的事务失效情况之this,其在createOrder中 隐式的调用是 this.insertOrderAndReduceStock(),此时就不是springboot所要的动态代理了,牢记第二点。
解决方法:
- 通过注入方式,使得orderService去调用insertOrderAndReduceStock方法,但是会导致循环依赖问题,通过注解@Lazy解决
- 通过AopContext中的currentProxy方法获取代理,在调用insertOrderAndReduceStock方法解决
我的BUG:
点击查看代码
@Override
public Result seckillVoucher(Long voucherId) {
// 1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2.判断秒杀是否开始
if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
// 此时 是开始时间大于当前时间,是不允许进来的,说明还没有开始秒杀环境
return Result.fail("秒杀尚未开始");
}
// 3.判断秒杀是否已经结束
//这行代码是用于判断优惠卷的结束时间是否早于当前时间。也就是说,如果当前时间已经超过了优惠卷结束使用的时间,则返回true
if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
// 此时 结束时间
return Result.fail("秒杀已经结束");
}
// 4.判断库存是否充足
if (voucher.getStock()<1) {
// 库存不足
return Result.fail("库存不足");
}
Long userId = UserHolder.getUser().getId();
// 加锁,根据每个用户的id 加锁,锁为userId,这个锁必须确保是唯一的,而toString方法底层是通过new 的方式说名不唯一,通过intern将
// 字符串放到常量池中,这样每一次一样的字符串地址值就一样了
synchronized (userId.toString().intern()) {
// spring在管理事务的时候,需要代理才可以生效,目前知道两种方式,1.自己调用自己 2.通过AopContext中的currentProxy 获取
// 获取到接口,需要在接口中实现 createVoucherOrder 方法
IVoucherOrderService proxy =(IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId,userId);
}
}
@Transactional(rollbackFor = Exception.class)
public Result createVoucherOrder(Long voucherId,Long userId) {
//5. 实现一人一单的功能
// 5.1 查询订单
Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
// 5.2 判断是否存在
if (count > 0) {
// 说明 用户有一单,抛出错误
return Result.fail("该用户已经购买过一次!");
}
// 6. 扣减库存
// update tb_seckill_voucher set stock = stock -1 where voucher_id = #{voucherId} and stock = #{stock}
// 经过测试发现,大多数线程都会访问失败,这是因为 大多数线程读到了相同的库存量,但是由于其他线程更改了,导致sql不执行
// 解决方法: 修改条件,将 stock = #{stock} 改为 stock > 0
boolean success = seckillVoucherService.update().setSql("stock = stock -1")
.eq("voucher_id", voucherId)
.gt("stock", 0)
.update();
if (!success) {
// 扣减失败
return Result.fail("库存不足");
}
// 7.创建订单
VoucherOrder voucherOrder = new VoucherOrder();
voucherOrder.setVoucherId(voucherId);
voucherOrder.setUserId(userId);
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
save(voucherOrder);
// 8.返回订单id
return Result.ok(orderId);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2021-08-12 Filter过滤器