【事务】在spring中事务不生效的场景总结

news2024/12/22 22:21:00

在开发过程中会遇到事务失效的问题,所以在开发中要特别注意,下面我自己总结了事务不生效的场景,提醒自己。
一般出现问题分为几大类:

  • 配置问题
  • spring aop代理问题
  • 底层数据库不支持事务问题
  • @Transactional 配置错误
  • 开发过程中使用错误问题

1.配置问题

1. 你的service类没有被Spring管理

//@Service 
public class TransactionalUserServiceImpl implements TransactionalUserService {

    @Resource
    TransactionalUserMapper transactionalUserMapper;

    @Override
    @Transactional
    public void transactionalError() throws Exception {
        TransactionalUser transactionalUser = new TransactionalUser();
        transactionalUser.setAsMsg("测试事务失效,修改成功");
        transactionalUser.setId("1");
        transactionalUserMapper.updateById(transactionalUser);
        throw new Exception();
    }
 }
  • 失败原因:@Service注解注释之后,spring事务(@Transactional)没有生效,因为Spring事务是由AOP机制实现的,也就是说从Spring IOC容器获取bean时,Spring会为目标类创建代理,来支持事务的。但是@Service被注释后,你的service类都不是spring管理的,所以无法创建代理。
  • 解决方法:加上@service注解,但是一般不会犯这种错误。

2. 没有开启事务管理器

  • 单独使用@Transactional,其实是不会生效的,因为它需要配置事务管理器开启事务。
  • 解决方法:springboot项目启动类要加入:@EnableTransactionManagement ,当然新版本它默认会自动配置事务管理器并开启事务支持。

2.spring aop代理问题

1.方法被final、static关键字修饰

   @Transactional
    public final void add(Addreq req) {
         //保存实体数据库记录
        addMapper.save(req);
        //保存流水数据库记录
        addFlowMapper.saveFlow(buildFlowById(req));
    }
  • 原因 :如果一个方法被声明为final或者static,则该方法不能被子类重写,也就是说无法在该方法上进行动态代理,这会导致Spring无法生成事务代理对象来管理事务。
  • 解决方法 :add事务方法 不要用final修饰或者static修饰。

2.同一个类中,方法内部调用

  @Override
    public void transactionalErrorV4() {
        this.transactionalUser();
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void transactionalUser() {
        TransactionalUser transactionalUser = new TransactionalUser();
        transactionalUser.setAsMsg("测试事务失效");
        transactionalUser.setId("2");
        transactionalUserMapper.updateById(transactionalUser);
    }
  • 原因 : 事务是通过Spring AOP代理来实现的,而在同一个类中,一个方法调用另一个方法时,调用方法直接调用目标方法的代码,而不是通过代理类进行调用 。即以上代码,调用目标transactionalUser方法不是通过代理类进行的,因此事务不生效。
  • 解决方法 :
    • 可以新建多一个类,让这两个方法分开,分别在不同的类中。
    • 也可以把主方法加上@Transactional,使他们用一个事务

3.方法的访问权限不是public

  @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
  private void transactionalUser() {
       TransactionalUser transactionalUser = new TransactionalUser();
       transactionalUser.setAsMsg("测试事务失效");
       transactionalUser.setId("2");
       transactionalUserMapper.updateById(transactionalUser);
   }
  • 原因 :spring事务方法transactionalUser的访问权限不是public,所以事务就不生效啦,因为Spring事务是由AOP机制实现的,AOP机制的本质就是动态代理,而代理的事务方法不是public的话,computeTransactionAttribute()就会返回null,也就是这时事务属性不存在了。大家可以看下AbstractFallbackTransactionAttributeSource的源码
  • 在这里插入图片描述

3.底层数据库不支持事务问题

Spring事务的底层,还是依赖于数据库本身的事务支持。在MySQL中,MyISAM存储引擎是不支持事务的,InnoDB引擎才支持事务。因此开发阶段设计表的时候,确认你的选择的存储引擎是支持事务的 。

4.@Transactional 配置错误

1.配置成只读事务

@Transactional(readOnly = true)
public void updateUser(User user) {
    userDao.updateUser(user);
}
  • 原因 :虽然使用了@Transactional注解,但是注解中的readOnly=true属性指示这是一个只读事务,因此在更新User实体时会抛出异常。
  • 解决方法:将readOnly属性设置为false,或者移除了@Transactional注解中的readOnly属性。

2.事务超时时间设置过短

@Transactional(timeout = 1)
public void doSomething() {
}
  • 原因 :在上面的例子中,timeout属性被设置为1秒,这意味着如果事务在1 秒内无法完成,则报事务超时了。

3.使用了错误的事务传播机制

//以非事务方式执行操作,如果当前存在一个事务,则将该事务挂起。
@Transactional(propagation = Propagation.NOT_SUPPORTED)

这里要科普下Spring提供了七种事务传播机制:

  1. REQUIRED(默认):如果当前存在一个事务,则加入该事务;否则,创建一个新事务。该传播级别表示方法必须在事务中执行。
  2. SUPPORTS:如果当前存在一个事务,则加入该事务;否则,以非事务的方式继续执行。
  3. MANDATORY:如果当前存在一个事务,则加入该事务;否则,抛出异常。
  4. REQUIRES_NEW:创建一个新的事务,并且如果存在一个事务,则将该事务挂起。
  5. NOT_SUPPORTED:以非事务方式执行操作,如果当前存在一个事务,则将该事务挂起。
  6. NEVER:以非事务方式执行操作,如果当前存在一个事务,则抛出异常。
  7. NESTED:如果当前存在一个事务,则在嵌套事务内执行。如果没有事务,则按REQUIRED传播级别执行。嵌套事务是外部事务的一部分,可以在外部事务提交或回滚时部分提交或回滚。

4.rollbackFor属性配置错误

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void transactionalErrorV6() {
        TransactionalUser transactionalUser = new TransactionalUser();
        transactionalUser.setAsMsg("测试事务失效");
        transactionalUser.setId("3");
        transactionalUserMapper.updateById(transactionalUser);
        throw new RuntimeException();
    }
  • 原因 : 其实rollbackFor属性指定的异常必须是Throwable或者其子类。默认情况下,RuntimeException和Error两种异常都是会自动回滚的。但是因为以上的代码例子,指定了rollbackFor = Exception.class,但是抛出的异常又是RuntimeException,因此事务就不生效。
  • 解决方法:要rollbackFor属性指定的异常与抛出的异常匹配。

5.使用不当

1.事务注解被覆盖导致事务失效

public interface A{
    @Transactional
    void save(String data);
}

public class AImpl implements A{
    @Override
    public void save(String data) {
        // 数据库操作
    }
}

public class BService {

    @Autowired
    private A A;

    @Transactional
    public void doSomething(String data) {
        A.save(data);
    }
}

public class CService extends BService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void doSomething(String data) {
        super.doSomething(data);
    }
}
  • 原因 :
    • CService 是 BService 的子类,并且覆盖了doSomething()方法。
    • 在该方法中,使用了不同的传播行为(REQUIRES_NEW)来覆盖父类的@Transactional注解。
    • 在这种情况下,当调用CService 的doSomething()方法时,由于子类方法中的注解覆盖了父类的注解,Spring框架将不会在父类的方法中启动事务 。
    • 因此,当A类的save()方法被调用时,事务将不会被启动,也不会回滚。
    • 这将导致数据不一致的问题,因为在A的save()方法中进行的数据库操作将不会回滚。
  • 解决:一般不建议业务调用链路太深,出现问题也不好排查。

2.嵌套事务

在开发中我们会执行两个及其以上的保存操作,有时候我们不需要全部回滚,但是如果没有特殊处理,出现问题都会回滚。

@Service
public class AService {

    @Autowired
    private BService bService;
    @Autowired
    private AMapper aMapper ;

    @Transactional
    public void addOrder(Order order) throws Exception {
        aMapper.save(order);
        bService.saveFlow(order);
    }
}

@Service
public class BService {

    @Autowired
    private BMapper bMapper;

    @Transactional(propagation = Propagation.NESTED)
    public void saveFlow(Order order) {
        bMapper.save(order);
        throw new RuntimeException();
    }
}
  • 原因:以上代码使用了嵌套事务,如果saveFlow出现运行时异常,会继续往上抛,到外层addOrder的方法,导致 aMapper.save(order);也会回滚。
  • 解决方法:如果不想因为被内部嵌套的事务影响 ,可以用try-catch包住,如下:
  @Transactional
  public void addOrder(Order order) throws Exception {
     aMapper.save(order);
      try {
          bService.saveFlow(order);
      } catch (Exception e) {
        log.error("savefail,message:{}",e.getMessage());
      }
  }

3.事务多线程调用

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void transactionalAsV11()  {
        new Thread(() -> {
            try {
                transactionalUser();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }

    public void transactionalUser() throws Exception {
        TransactionalUser transactionalUser = new TransactionalUser();
        transactionalUser.setAsMsg("测试事务失效");
        transactionalUser.setId("2");
        transactionalUserMapper.updateById(transactionalUser);
        throw new Exception();
    }
  • 原因 :这是因为Spring事务是基于线程绑定的,每个线程都有自己的事务上下文 ,而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务不生效。Spring事务管理器通过使用线程本地变量(ThreadLocal)来实现线程安全。
  • 解决方法:通过TransactionAspectSupport 来手动设置回滚
   public void transactionalUser() throws Exception {
        //设置回滚点,只回滚以下异常
        Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
        try {
            TransactionalUser transactionalUser = new TransactionalUser();
            transactionalUser.setAsMsg("测试事务失效");
            transactionalUser.setId("2");
            transactionalUserMapper.updateById(transactionalUser);
            throw new Exception();
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
        }
    }

还有一种方式是使用springboot的线程注解: @Async

    @Override
    @Async("MyExecutor")
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = RuntimeException.class)
    public void transactionalAsV12() throws InterruptedException {
        Thread.sleep(10000);
        TransactionalUser transactionalUser = new TransactionalUser();
        transactionalUser.setAsMsg("测试事务失效");
        transactionalUser.setId("2");
        transactionalUserMapper.updateById(transactionalUser);
        throw new RuntimeException();
    }

上面写法事务是生效的,但是使用注解有几个注意点需要注意一下:
如下方式会使@Async失效

  • 异步方法使用static修饰
  • 异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类
  • 异步方法不能与被调用的异步方法在同一个类中
  • 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
  • 如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解

4.异常没有抛出

 @Transactional
  public void addOrder(Order order) {
       try {   
           aMapper.save(order);
           bFlowMapper.saveFlow(order);
       } catch (Exception e) {
           log.error("add error,id:{},message:{}", order.getId(),e.getMessage());
       }
   }
  • 原因 : 事务中的异常已经被业务代码捕获并处理,而没有被正确地传播回事务管理器,事务将无法回滚。我们可以从spring源码(TransactionAspectSupport这个类)中找到答案:
    在这里插入图片描述在invokeWithinTransaction方法中,当Spring catch到Throwable异常的时候,就会调用completeTransactionAfterThrowing()方法进行事务回滚的逻辑。但是,在addOrder 事务方法中,直接把异常catch住了,并没有重新throw出来,因此 Spring自然就catch不到异常,因此事务回滚的逻辑就不会执行,事务就失效了。

解决方法:抛出异常就好了

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = RuntimeException.class)
    public void transactionalAsV13() {
        try {
            TransactionalUser transactionalUser = new TransactionalUser();
            transactionalUser.setAsMsg("测试事务失效");
            transactionalUser.setId("2");
            transactionalUserMapper.updateById(transactionalUser);
            throw new RuntimeException();
        } catch (RuntimeException e) {
            System.out.println("吞并异常");
            throw new RuntimeException();
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/493486.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Go有序map:orderedmap

有序映射 与传统的无序映射(Map)不同,orderedmap包中的有序映射(OrderedMap)可以记录键值对的插入顺序。orderedmap提供了一些有用的API,用来存储、删除、查询和遍历键值对。 获取OrderedMap 你可以通过Ord…

地面分割--Fast Segmentation of 3D Point Clouds for Ground Vehicles论文阅读与源码分析

文章目录 1写在前面的话2点云投影分块3地面点云分割4核心代码阅读投影分块直线拟合代码分割地面点云 5实验效果参考 1写在前面的话 这篇文章属于地面分割领域非常经典的一篇论文,论文具有速度快,在一定程度能适应有坡度的地形,文章主要分为两…

学习使用ansible自动化运维工具

目录 一、虚拟机环境 二、yum方式部署 三、ansible使用 (一)将ansible服务器上文件分发给各节点 1. 创建一个要复制的文件,并复制到Ansible管理主机上 2.编辑Ansible的playbook文件,将copy模块添加到任务列表中 3. 运行play…

【c++迭代器模拟实现】

目录: 前言一、STL初始二、六大组件之迭代器迭代器初始迭代器的模拟实现(1)victor正向迭代器反向迭代器1反向迭代器2反向迭代器3 (2)list正向迭代器反向迭代器 总结 前言 打怪升级:第52天 一、STL初始 什…

和chatgpt一样的大模型LLaMA可以运行在pc上?

未来已来,大模型依据压缩模型的方式,可以在普通的PC上运行. LLaMA Facebook的LLaMA 模型和Georgi Gerganov 的llama.cpp的结合。 LLaMA,这是一组包含 7B 到 65B 参数的基础语言模型。我们在数万亿个令牌上训练我们的模型,并表明可以仅使用公开可用的数…

【Android入门到项目实战-- 9.1】—— 传感器的使用教程

目录 传感器的定义 三大类型传感器 1、运动传感器 2、环境传感器 3、位置传感器 传感器开发框架 1、SensorManager 2、Sensor 3、SensorEvent 4、SensorEventListener 一、使用传感器开发步骤 1、获取传感器信息 1)、获取传感器管理器 2)、获取设备的传感器对象列…

Java红黑树

概述 红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。1972年出现的,当时被称之为平衡二叉B树。在1978年被修改为红黑树。红黑树是一种特殊的二叉查找树,红黑树上的每一个节点都有存储位表示节点的颜色。每一个节点可以是…

Java枚举:为什么它是单例模式的最佳选择?

前言 单例模式,是工作中比较常见的一种设计模式,通常有两种实现方式,懒汉式和饿汉式。但是这两种实现方式存在一些问题。懒汉式需要在多线程环境下使用同步锁机制来保证只有一个实例被创建,这会影响程序的性能。而饿汉式在类加载时…

《发展心理学——儿童与青少年》读书笔记

这是我读的第一本关于育儿教育类的书,该书的作者是David R. Shaffer,由北京师范大学博士生导师邹泓审校,由其底下的博士生们翻译。我看的是中文第九版。下面是我在阅读此书时做的关键摘录和部分感想: 第1章 导论:发展心理学及其研…

Java基础(二十一):集合源码

Java基础系列文章 Java基础(一):语言概述 Java基础(二):原码、反码、补码及进制之间的运算 Java基础(三):数据类型与进制 Java基础(四):逻辑运算符和位运算符 Java基础(五):流程控制语句 Java基础(六)&#xff1…

耗时2.5h含泪打造windows10家庭版docker安装

文章目录 一、事出有因二、安装流程Problem1Problem2Problem3 三、胜利的曙光 一、事出有因 由于最近需要跑通github上的一个代码,那个github上的代码需要通过docker部署到本地,但是我的电脑上并没有docker,真的是含泪历时2.5h才把docker在我的windows电…

【Python成长之路】基于Flask-admin库,结合html+vue,实现前后端数据传递

一、前言 前面已经做了Flask-admin库的基本介绍和几个库常用功能如何使用,若不了解请移步到以下博客: 1、?《【Python成长之路】基于Flask-admin库,编写个人工作平台代码详述》 2、?《【Python成长之路】基于Flask-admin库,编…

DP练习题

1.减操作(ACWING) 若有 a b c d e f g 几个数, 先对位置d操作 变成 a b c d - e f g 再对c操作 变成 a b c - (d-e) f g 仔细分析后得出结论:对于第一个数如a, 它一定为正数,第二个数b,一定为负数&#…

Java并发(四)----线程运行原理

1、线程运行原理 1.1 栈与栈帧   Java Virtual Machine Stacks (Java 虚拟机栈 JVM) 我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分…

java 解密springboot的WEB端口是谁启动的之内嵌tomcat

找到项目的 pom.xml 看到下面的spring-boot-starter-web 我们按住 Ctrl 点击进去 里面就有一个 tomcat 简单说 我们的程序能启动起tomcat端口 就是靠的这个东西 简单说 就是在程序中嵌了一个tomcat服务器 这里 可能就有小伙伴蒙了 不是把程序放在服务器上运行吗&#xff1f…

Linux Driver 和Device匹配过程分析(2)

Linux Driver 和Device匹配过程分析(2) 1 device注册流程2,driver注册匹配过程:2.1 pci_register_driver2.1.1 nvme_init2.1.2 pci_register_driver2.1.3 __pci_register_driver2.1.4 driver_register2.1.5 bus_add_driver2.1.6 d…

读书笔记——《2001太空漫游》

阿瑟克拉克神作,任何一个科幻迷都绕不开的一部作品。很早就听说过其大名,因为之前看过电影版的,总感觉少了点新鲜感,这本书就一直在书架上没有拿出来看。但是看过这本书后,我可以很负责任的说,全书都充满新…

【递推专题】常见的递推“模型”总结

目录 1.斐波那契数列分析:代码: 2.平面分割问题分析: 3.汉诺塔问题分析: 4.卡特兰数分析: 5.第二类斯特林数总结: 1.斐波那契数列 分析: 斐波那契数列又称兔子数列,其原理来源于兔子…

dangerousRemoteUrlIpcAccess

问题描述: 在使用Tauri窗口加载外部链接时,需要也能继续使用Tauri API与Rust交互。按照官方发布通告中的代码添加配置: "security": {"dangerousRemoteUrlIpcAccess": [ { "windows": ["main", &qu…

在Linux中进行Jenkins部署(maven-3.9.1+jdk8)

Jenkins部署在公网IP为x.x.x.x的服务器上 maven-3.9.1要安装在jdk8环境中 环境准备 第一步,下载server-jre-8u202-linux-x64.tar.gz安装包。 登录地址:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html下载server-j…