介绍
Spring Boot是一个开源的Java框架,它提供了一种简单的方法来管理事务。在Spring Boot中,你可以使用@Transactional
注解来启用事务管理。这个注解可以放在类级别或方法级别上。当放在类级别上时,它表示整个类中的所有方法都会在同一个事务中执行。当放在方法级别上时,它表示该方法将在一个新的事务中执行。
问题分析
使用Spring Boot进行事务管理的好处是,你不需要手动配置数据库连接和事务管理器。你只需要添加相应的依赖项,并使用@Transactional
注解即可。
但是由于这样的事务支持是基于aop切面的,也就是基于动态代理的,所以该事务会有失效的风险,以下就是一个典型的例子
@Override
public Result seckillVoucher(Long voucherId) {
// 1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2.判断秒杀是否开始
if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
// 尚未开始
return Result.fail("秒杀尚未开始!");
}
// 3.判断秒杀是否已经结束
if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
// 尚未开始
return Result.fail("秒杀已经结束!");
}
// 4.判断库存是否充足
if (voucher.getStock() < 1) {
// 库存不足
return Result.fail("库存不足!");
}
return this.createVoucherOrder(voucherId);
}
@Transactional
public Result createVoucherOrder(Long voucherId) {
// 5.一人一单
Long userId = UserHolder.getUser().getId();
// 创建锁对象
RLock redisLock = redissonClient.getLock("lock:order:" + userId);
// 尝试获取锁
boolean isLock = redisLock.tryLock();
// 判断
if(!isLock){
// 获取锁失败,直接返回失败或者重试
return Result.fail("不允许重复下单!");
}
try {
// 5.1.查询订单
int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
// 5.2.判断是否存在
if (count > 0) {
// 用户已经购买过了
return Result.fail("用户已经购买过一次!");
}
// 6.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1") // set stock = stock - 1
.eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0
.update();
if (!success) {
// 扣减失败
return Result.fail("库存不足!");
}
// 7.创建订单
VoucherOrder voucherOrder = new VoucherOrder();
// 7.1.订单id
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
// 7.2.用户id
voucherOrder.setUserId(userId);
// 7.3.代金券id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
// 7.返回订单id
return Result.ok(orderId);
} finally {
// 释放锁
redisLock.unlock();
}
}
在seckillVoucher方法的最后,我们调用了this.createVoucherOder方法,而这个原方法中是添加了@Transactional事务注解的,但是由于我们在seckillVoucher方法中是通过this关键字去调用的,实际调用的就是this.createVoucherOder方法本身,而不是spring框架帮我们创建的动态代理类(添加了事务支持)的那个createVoucherOder方法,最终导致了事务的失效。