带有@Transactional注解的方法事务失效问题以及解决方法

介绍

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方法,最终导致了事务的失效。

解决方法

1.引入aspectj依赖

2.在springboot启动类的@EnableAspectJAutoProxy注解中将设置是否暴露代理对象的属性exposeProxy设置为true,默认为false

带有@Transactional注解的方法事务失效问题以及解决方法_第1张图片

3.使用相关api ,AopContext.currentProxy()拿到当前类的代理对象,使用代理对象调用方法即可

你可能感兴趣的:(Java后端开发,spring,boot,数据库)