• Spring第六讲:Spring事务原理及应用


    摘要:面试时常常被面试官问到这个问题。Spring框架中的核心技术:控制反转/依赖注入/面向切面编程/Spring的声明式事务/以及Spring生态相关产品的简介,本文是Spring第六讲:Spring事务原理及应用

    1、 请描述一下Spring的事务(事务的实现方式+事务底层原理)

    1.1、编程式事务与声明式事务

    1.1.1、编程式事务:是指在代码中手动管理事务的提交、回滚等操作,代码侵入性比较强

    如下示例:

    public void update(Integer id, String name, Integer age, Integer marks, Integer year) {
        TransactionDefinition def = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            String SQL1 = "update Student set age = ?,name = ? where id = ?";
            jdbcTemplateObject.update(SQL1, age,name, id);
            System.out.println("Updated Record with ID = " + id);
            String SQL2 = "update Marks set marks = ?,year = ? where sid = ?";
            jdbcTemplateObject.update(SQL2, marks,year, id);
            System.out.println("Updated Record with SID = " + id);
            transactionManager.commit(status);
        } catch (DataAccessException e) {
            System.out.println("Error in creating record, rolling back");
            transactionManager.rollback(status);
            throw e;
        }
        return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1.1.2、声明式事务:基于AOP面向切面的

    它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式

    基于TX和AOP的xml配置文件方式,已经过时

    步骤:

    1、配置事务管理器

    
     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
         <property name="sessionFactory" ref="sessionFactory">
     <bean/>
    
    • 1
    • 2
    • 3
    • 4

    2、配置注解事务

    
     
     <tx:annotation-driven transaction-manager="transactionManager"/>
     <!--配置advice通知    ->
     
     <tx:advice id="txAdvice" transaction-manager="transactionManager">
         <tx:attributes> 
         	 
             
             <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="" read-only="false" no-rollback-for="" rollback-for=""/>
             
             <tx:method name="*" propagation="SUPPORTS"/> 
         tx:attributes> 
     tx:advice> 
      
     <aop:config> 
         <aop:pointcut id="interceptorPointCuts" 
                 expression="execution(* cn.gov.zcy.service.*.manager.*.*(..))” id="pointcut"/>
         
         <aop:advisor advice-ref="txAdvice" 
                 pointcut-ref="interceptorPointCuts"/>
     aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    execution 语法结构
    execution([权限修饰符][返回类型][类全路径]方法名称)
    execution(com.bluesky.spring.dao..*(…)) 所有方法进行增强

    基于@Transactional 注解

    • @Transactional 可以注解在接口、接口方法、类和类方法上。当作用于类上时,该类的一切 public 方法将都具有该类型的事务属性。

    spring事务配置
    TransactionManager----datasourcetransactionManager // jdbc配置
    ----hibernatetransactionManager // hibernate配置

    代理机制----bean和代理 --每个bean有一个代理
    –所有bean共享一个代理基类
    ----使用拦截器
    ----使用tx标签配置的拦截器
    ----全注解配置

    基于TransactionInterceptor的声明式事务管理:

    • 两个次要的属性:
      • transactionManager,用来指定一个事务治理器,并将具体事务相关的操作请托给它;
      • Properties 类型的transactionAttributes 属性,该属性的每一个键值对中,键指定的是方法名,方法名可以行使通配符,而值就是表现呼应方法的所运用的事务属性。

    代码示例如下

    @Transactional 
     public class AccountServiceImpl implements AccountService{}
    
    • 1
    • 2
    • 结论:事务的操作本来应该是由数据库来进行控制,但是为了方便用户进行业务逻辑的操作,Spring对事务功能进行了扩展实现,一般我们很少使用编程式事务,更多的是通过添加 @Transactional 注解来进行实现,当添加此注解后事务的自动功能就会关闭,由Spring框架来帮忙进行控制。

    1.2、@Transactional注解介绍

    1.2.1、@Transactional注解可以作用于哪些地方?
    • @Transactional 可以作用在接口、类、类方法。
      • 作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。
      • 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。
      • 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效

    代码示例如下:

    @Transactional
    @RestController
    @RequestMapping
    publicclass MybatisPlusController {
        @Autowired
        private CityInfoDictMapper cityInfoDictMapper;
        @Transactional(rollbackFor = Exception.class)
        @GetMapping("/test")
        public String test() throws Exception {
            CityInfoDict cityInfoDict = new CityInfoDict();
            cityInfoDict.setParentCityId(2);
            cityInfoDict.setCityName("2");
            cityInfoDict.setCityLevel("2");
            cityInfoDict.setCityCode("2");
            int insert = cityInfoDictMapper.insert(cityInfoDict);
            return insert + "";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1.2.2、@Transactional注解有哪些属性?

    Spring 对事务控制的支持统一在 TransactionDefinition 类中描述,该类有以下几个重要的接口方法:

    • int getPropagationBehavior():事务的传播行为:就是多个事务方法相互调用时,事务如何在这些方法间传播

      • REQUIRED 支持当前事务,不存在就新建一个 默认(默认的隔离级别与各个数据库一致)
      • required new 如果有当前事务,挂起当前事务,创建一个新的事务
      • nested 如果有当前事务,嵌套事务执行
        • 假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这3个服务类的3个方法通过Spring的事务传播机制都工作在同一个事务中
    • int getIsolationLevel():事务的隔离级别

    • int getTimeout():事务的过期时间

    • boolean isReadOnly():事务的读写特性。

    • 传播行为是解决什么问题呢?

      • 多个事务方法相互调用时,事务是如何在这些方法之间进行传播的。
      • 例如 A类有个方法a,B类有个方法b,如果在a方法里面调用b方法,那么应当执行什么样的事务策略呢,看配置的propagation属性
    • propagation属性(共7种)

      • 定义:propagation 代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下:
        • ①Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。
          • 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务
          • 默认
        • ②Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
        • ③Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
        • ④Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。
          • 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务
        • ⑤Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。
        • ⑥Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。
          • 几乎没有使用场景
        • ⑦Propagation.NESTED :和 Propagation.REQUIRED 效果一样。
    • isolation 属性

      • isolation:事务的隔离级别,默认值为 Isolation.DEFAULT。
        • TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,
          • Mysql 默认采用的 REPEATABLE_READ隔离级别
          • Oracle 默认采用的 READ_COMMITTED隔离级别.
        • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读.
          • 没有使用场景
        • TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
        • TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
        • TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
          • 但是这将严重影响程序的性能。通常情况下也不会用到该级别,没有使用场景
    • timeout 属性

      • timeout:事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
    • readOnly 属性

      • readOnly:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
    • rollbackFor 属性

      • rollbackFor:用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
    • noRollbackFor属性

      • noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
    1.2.3、事务执行流程图

    在这里插入图片描述

    1.3、Spring事务的底层实现原理

    1.3.1、事务中的主类

    1、TransactionProxyFactoryBean

    • 管理事务代理对象的工厂bean
    • 创建代理对象

    2、TransactionAttributeSourceAdvisor

    • Spring事务切入点通知器
    • 定义cutPoint,和对应的Interceptor

    3、TransactionInterceptor

    • 事务拦截器
    • 调用 TransactionAspectSupport.invokeWithinTransaction方法开始执行切面事务

    4、TransactionAspectSupport

    • 切面事务逻辑主类,声明事务+反射调用全流程
    • 1、组装创建事务前置条件信息
    • 2、创建事务
    • 3、反射调用真正业务逻辑
    • 4、事务的提交和回滚

    5、AbstractPlatformTransactionManager

    • 抽象事务管理器,声明事务中操作模板,封装通用部分,屏蔽TransactionStatus的流转细节,由具体的事务管理器(类似DataSourceTransactionManager或HibernateTransactionManager等)来实现具体逻辑,如 doGetTransaction,doBegin,doCommit,doRollback,isExistingTracsaction等逻辑实现

    6、TransactionStatus

    • 事务状态操作接口类
    • 由具体的实现类类实现状态操作接口(如DefaultTransactionStatus)

    7、TransactionInfo

    • 事务信息
    • 描述一个事务内所涵盖的所有信息,如下
      • 1、事务管理器
      • 2、事务属性(隔离级别、传播行为等)
      • 3、事务状态
      • 4、事务切点
    1.3.2、使用AOP实现原理
    • 当一个方法添加 @Transactional 注解之后,Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当使用这个代理对象的方法时,如果有事务处理,那么会先把事务的自动提交给关闭,然后去执行具体的业务逻辑,如果执行逻辑没有出现异常,那么代理逻辑就会直接提交,如果出现任何异常情况,那么直接进行回滚操作,当然,用户可以控制对那些异常进行回滚操作。

    AOP原理:

    • AOP在进行解析的时候,最终生成一个Advisor对象,这个Advisor对象中封装了切面织入所需要的所有信息,其中就包括Aspect:他是跨不同java类层面的横切性逻辑,实现形式上,可以是XML文件中配置的普通类,也可在类代码中用“@Aspect”注解声明,运行时spring框架创建Advisor来指代他:(源码中对应BeanFactoryTransactionAttributeSourceAdvisor)

    Advisor最重要的两个部分:PointCut和Advice属性。

    • 切入的时机pointCut:判断目标bean是否需要织入当前事务逻辑;(为了使切点复用,利用@PointCut专门定义拦截规则)代码中对应TransactionAttributeSourcePointcut
    • 切入的动作Advice:封装了需要织入的切面逻辑 ;代码中对应TransactionInterceptor

    Join Point:它是Aspect可以切入的特定点,在Spring里面只有方法可以作为Join Point(是可利用的机会,具体由pointcut指定)
    Advice:它定义了切面中能够采取的动作。如果去看Spring源码,就会发现Advice、Join Point并没有定义在Spring自己的命名空间里,这是因为他们是源自AOP联盟,可以看作是Java工程师在AOP层面沟通的通用规范

    • 通常将拦截器类型的Advice叫作Around,在代码中可以使用“@Around”来标记,或者在配置中使用“ < aop:around>”

    分别解释这三个类:

    • BeanFactoryTransactionAttributeSourceAdvisor:封装了实现事务所需的所有属性,包括pointCut,advice,transactionManager以及一些其他在Transactional注解中声明的属性;
    • TransactionAttributeSourcePointcut:判断目标bean是否需要织入当前事务逻辑,依据就是当前方法或类声明上有没有使用@Transactional注解;
    • TransactionInterceptor : 封装了需要织入的切面逻辑 ,Spring事务是借助数据库事务来实现对目标方法的环绕的。

    总结:Spring支持AspectJ的注解式切面编程
    1、使用@Aspect声明是一个切面;
    2、使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数;
    3、其中@After、@Before、@Around参数的拦截规则为切点(PointCut),为了使切点复用,可使用@PointCut专门定义拦截规则;
    4、其中符合条件的每一个被拦截处为连接点(JointPoint)

    示例:
    1、添加Spring AOP支持及AsectJ依赖

    • aop/aspectjrt/aspectjweaver

    2、编写拦截规则的注解(编写注解)

     @Target(ElementType.METHOD)
     @Retention(RetentionPolicy.RUNTIME)
     @Documented
     public @interface Action{
         String name();//注解是一种元数据,即解释数据的数据
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3、编写使用注解的被拦截类(使用注解)

    @Service
     public class DemoAnnotationService{
         @Action(name="拦截式拦截的add操作")
         public void add(){}
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4、编写切面

     @Aspect      //切面
     @Component   //让此切面加入IOC容器
     public class LogAspect{
         @PointCut("@annotation(com.wisely.aop.Action)")
         public void annotationPointCut(){};
         //后置处理逻辑,在切入点方法执行后执行
         @After("anntationPointCut")
         public void after(JointPoint jointPoint){
             MethodSignature signature=(MethodSignature)jointPoint.getSignature();
             Method method =getSignature.getMethod();
             Action action = method.getAnnotation(Action.class);
             sout("Q"+action.name());
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5、配置类

    • 使用@EnableAspectJAutoProxy注解开启Spring对AspectJ的支持

    6、运行

    1.3.3、事务实现原理-整体流程

    在这里插入图片描述

    1.3.4、事务代理对象、拦截器建立过程

    在这里插入图片描述

    1.3.5、事务拦截及处理过程

    在这里插入图片描述

    1.3.6、createTransactionIfNecessary 方法处理过程

    在这里插入图片描述

    1.3.7、DataSourceTransactionManager.doBegin() 方法处理过程

    在这里插入图片描述

    1.3.8、Mybatis 查询db过程

    在这里插入图片描述

    1.3.9、Spring + Mybatis 事务中查询db过程

    在这里插入图片描述

    1.3.10、多事务管理器

    在这里插入图片描述
    Demo示例如下:

    @Configuration
    public class TransactionManagerConfiguration {
        /**
         * 配置RDS数据源事务管理器
         * @return
         */
        @Bean(name = "drdsTransactionManager")
        public PlatformTransactionManager drdsTransactionManager(DynamicDataSource dynamicDataSource){
            DataSource targetDataSourceByUserDefinedKey = dynamicDataSource.getTargetDataSourceByUserDefinedKey("drds");
            DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(targetDataSourceByUserDefinedKey);
            return dataSourceTransactionManager;
        }
    
        @Bean(name = "drdsSqlSessionFactory")
        public SqlSessionFactory drdsSqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception {
            DataSource targetDataSourceByUserDefinedKey = dynamicDataSource.getTargetDataSourceByUserDefinedKey("drds");
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(targetDataSourceByUserDefinedKey);
            sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/itemMapper.xml"));
            sqlSessionFactoryBean.setTypeAliasesPackage("cn.gov.zcy.service.item.domain");
            return sqlSessionFactoryBean.getObject();
        }
    
        @Bean(name = "drdsSqlSessionTemplate")
        public SqlSessionTemplate drdsSqlSessionTemplate(@Qualifier("drdsSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
            SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
            return sqlSessionTemplate;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    1.3.11、分布式事务

    见这篇文章:MySQL第七讲:数据库事务及MVCC机制/分布式事务实战


    2、 六种 @Transactional 注解失效场景 面试必备

    使用@Transactional注解时需要注意许多的细节,不然你会发现@Transactional总是莫名其妙的就失效了

    • 1、@Transactional 应用在非 public 修饰的方法上
      如果Transactional注解应用在非public 修饰的方法上,Transactional将会失效
      在这里插入图片描述
      之所以会失效是因为在Spring AOP 代理时,如上图所示 TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。
    protected TransactionAttribute computeTransactionAttribute(Method method,
        Class<?> targetClass) {
            // Don't allow no-public methods as required.
            if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
            return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。
      注意:protected、private 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点

    • 2、@Transactional 注解属性 propagation 设置错误
      这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。

      • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
      • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
      • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
    • 3、@Transactional 注解属性 rollbackFor 设置错误
      rollbackFor 可以指定能够触发事务回滚的异常类型Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。
      在这里插入图片描述

    // 希望自定义的异常可以进行回滚
    @Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class
    
    • 1
    • 2
    • 若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。Spring 源码如下:
    private int getDepth(Class<?> exceptionClass, int depth) {
        if (exceptionClass.getName().contains(this.exceptionName)) {
              // Found it!
              return depth;
    	}
        // If we've gone as far as we can go and haven't found it...
        if (exceptionClass == Throwable.class) {
            return -1;
    	}
    	return getDepth(exceptionClass.getSuperclass(), depth + 1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 4、同一个类中方法调用,导致@Transactional失效开发中遇到过这种场景
      开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方

    那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理

    //@Transactional
        @GetMapping("/test")
        private Integer A() throws Exception {
            CityInfoDict cityInfoDict = new CityInfoDict();
            cityInfoDict.setCityName("2");
            /**
             * B 插入字段为 3的数据
             */
            this.insertB();
            /**
             * A 插入字段为 2的数据
             */
            int insert = cityInfoDictMapper.insert(cityInfoDict);
    
            return insert;
        }
    
        @Transactional()
        public Integer insertB() throws Exception {
            CityInfoDict cityInfoDict = new CityInfoDict();
            cityInfoDict.setCityName("3");
            cityInfoDict.setParentCityId(3);
    
            return cityInfoDictMapper.insert(cityInfoDict);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    解决方案

    • 在事务内部调用事务时,对调用的方法采用ApplicationContextUtil 来管理,使得 Spring容器能管理事务。
    AgGoodsManager agGoodsManager = (AgGoodsManager) ApplicationContextUtil.getBean("agGoodsManager");
    
    • 1
    • 5、异常被你的 catch“吃了”导致@Transactional失效
      这种情况是最常见的一种 @Transactional 注解失效场景
    @Transactional
    private Integer A() throws Exception {
        int insert = 0;
        try {
            CityInfoDict cityInfoDict = new CityInfoDict();
            cityInfoDict.setCityName("2");
            cityInfoDict.setParentCityId(2);
            /**
             * A 插入字段为 2的数据
             */
            insert = cityInfoDictMapper.insert(cityInfoDict);
            /**
             * B 插入字段为 3的数据
             */
            b.insertB();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?

    答案:不能!
    会抛出异常:

    org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
    
    • 1
    • 因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。

    • spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。

    • 在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据commit造成数据不一致,所以有些时候 try catch反倒会画蛇添足。

    • 6、数据库引擎不支持事务
      这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。

    参考资料

    吃得苦中苦,方为人上人

  • 相关阅读:
    抽象轻松的C语言
    什么是华为认证-Datacom数据通信工程师?
    springboot毕设项目大学生创新项目运维系统9y232(java+VUE+Mybatis+Maven+Mysql)
    深入理解Python协程:从基础到实战
    swift UI 和UIKIT 如何配合使用
    SpringSecurity基础概念和案例代码
    科技型中小企业有哪些?
    自动求导,计算图示意图及pytorch实现
    机械臂的零力拖动和直接示教
    java aspose cells 读取名称管理器
  • 原文地址:https://blog.csdn.net/qq_28959087/article/details/125886937