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


    介绍

    Spring Boot是一个开源的Java框架,它提供了一种简单的方法来管理事务。在Spring Boot中,你可以使用@Transactional注解来启用事务管理。这个注解可以放在类级别或方法级别上。当放在类级别上时,它表示整个类中的所有方法都会在同一个事务中执行。当放在方法级别上时,它表示该方法将在一个新的事务中执行。

    问题分析

    使用Spring Boot进行事务管理的好处是,你不需要手动配置数据库连接和事务管理器。你只需要添加相应的依赖项,并使用@Transactional注解即可。

    但是由于这样的事务支持是基于aop切面的,也就是基于动态代理的,所以该事务会有失效的风险,以下就是一个典型的例子

    1. @Override
    2. public Result seckillVoucher(Long voucherId) {
    3. // 1.查询优惠券
    4. SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
    5. // 2.判断秒杀是否开始
    6. if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
    7. // 尚未开始
    8. return Result.fail("秒杀尚未开始!");
    9. }
    10. // 3.判断秒杀是否已经结束
    11. if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
    12. // 尚未开始
    13. return Result.fail("秒杀已经结束!");
    14. }
    15. // 4.判断库存是否充足
    16. if (voucher.getStock() < 1) {
    17. // 库存不足
    18. return Result.fail("库存不足!");
    19. }
    20. return this.createVoucherOrder(voucherId);
    21. }
    22. @Transactional
    23. public Result createVoucherOrder(Long voucherId) {
    24. // 5.一人一单
    25. Long userId = UserHolder.getUser().getId();
    26. // 创建锁对象
    27. RLock redisLock = redissonClient.getLock("lock:order:" + userId);
    28. // 尝试获取锁
    29. boolean isLock = redisLock.tryLock();
    30. // 判断
    31. if(!isLock){
    32. // 获取锁失败,直接返回失败或者重试
    33. return Result.fail("不允许重复下单!");
    34. }
    35. try {
    36. // 5.1.查询订单
    37. int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
    38. // 5.2.判断是否存在
    39. if (count > 0) {
    40. // 用户已经购买过了
    41. return Result.fail("用户已经购买过一次!");
    42. }
    43. // 6.扣减库存
    44. boolean success = seckillVoucherService.update()
    45. .setSql("stock = stock - 1") // set stock = stock - 1
    46. .eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0
    47. .update();
    48. if (!success) {
    49. // 扣减失败
    50. return Result.fail("库存不足!");
    51. }
    52. // 7.创建订单
    53. VoucherOrder voucherOrder = new VoucherOrder();
    54. // 7.1.订单id
    55. long orderId = redisIdWorker.nextId("order");
    56. voucherOrder.setId(orderId);
    57. // 7.2.用户id
    58. voucherOrder.setUserId(userId);
    59. // 7.3.代金券id
    60. voucherOrder.setVoucherId(voucherId);
    61. save(voucherOrder);
    62. // 7.返回订单id
    63. return Result.ok(orderId);
    64. } finally {
    65. // 释放锁
    66. redisLock.unlock();
    67. }
    68. }

    在seckillVoucher方法的最后,我们调用了this.createVoucherOder方法,而这个原方法中是添加了@Transactional事务注解的,但是由于我们在seckillVoucher方法中是通过this关键字去调用的,实际调用的就是this.createVoucherOder方法本身,而不是spring框架帮我们创建的动态代理类(添加了事务支持)的那个createVoucherOder方法,最终导致了事务的失效。

    解决方法

    1.引入aspectj依赖

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

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

  • 相关阅读:
    网络安全渗透测试工具之skipfish
    电商平台的支付解决方案
    25-什么是事件循环
    Python期末复习题库(上)——“Python”
    centos下安装nginx
    搭建AE脚本开发环境
    《uni-app》一个非canvas的飞机对战小游戏-启动页
    C语言-数据结构-单向链表
    Java.lang.Class类 getTypeParameters()方法有什么功能呢?
    Docker手把手教程(四)Dockerfile完全指南
  • 原文地址:https://blog.csdn.net/m0_61721601/article/details/132815609