IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring第六讲:Spring事务原理及应用 -> 正文阅读

[Java知识库]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.1.2、声明式事务:基于AOP面向切面的

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

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

步骤:

1、配置事务管理器

<!--定义事务管理器  声明式的事务-->
 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <property name="sessionFactory" ref="sessionFactory">
 <bean/>

2、配置注解事务

<!-- xml开启注解事务 -->
 <!--主要用于事务驱动,其会通过AOP的方式声明一个为事务支持的Advisor,通过该Advisor和事务的相关配置进行事务相关操作-->
 <tx:annotation-driven transaction-manager="transactionManager"/>
 <!--配置advice通知    ->
 <!-- <tx:advice>定义事务通知,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过<tx:attributes> 指定具体需要拦截的方法 -->
 <tx:advice id="txAdvice" transaction-manager="transactionManager">
     <tx:attributes> 
     	 <!-- <tx:method>拦截方法,其中参数有:
			name:方法名称,将匹配的方法注入事务管理,可用通配符
			propagation:事务传播行为,
			isolation:事务隔离级别定义;默认为“DEFAULT”
			timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
			read-only:事务只读设置,默认为false,表示不是只读;
			rollback-for:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚;
			no-rollback-for:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割; -->
         <!-- 拦截save开头的方法,事务传播行为为:REQUIRED:必须要有事务, 如果没有就在上下文创建一个 -->
         <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> 
 <!-- 配置切点切面 expression为切人点表达式,如下是指定impl包下的所有方法,具体以自身实际要求自定义 --> 
 <aop:config> 
     <aop:pointcut id="interceptorPointCuts" 
             expression="execution(* cn.gov.zcy.service.*.manager.*.*(..))” id="pointcut"/>
     <!-- <aop:advisor>定义切入点,与通知,把tx与aop的配置关联,才是完整的声明事务配置 -->
     <aop:advisor advice-ref="txAdvice" 
             pointcut-ref="interceptorPointCuts"/>
 </aop:config>

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{}
  • 结论:事务的操作本来应该是由数据库来进行控制,但是为了方便用户进行业务逻辑的操作,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.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();//注解是一种元数据,即解释数据的数据
 }

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

@Service
 public class DemoAnnotationService{
     @Action(name="拦截式拦截的add操作")
     public void add(){}
 }

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());
     }
 }

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.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;
}
  • 此方法会检查目标方法的修饰符是否为 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
  • 若在目标方法中抛出的异常是 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);
}
  • 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);
    }

解决方案

  • 在事务内部调用事务时,对调用的方法采用ApplicationContextUtil 来管理,使得 Spring容器能管理事务。
AgGoodsManager agGoodsManager = (AgGoodsManager) ApplicationContextUtil.getBean("agGoodsManager");
  • 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();
    }
}

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

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

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
  • 因为当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,那事务就从根本上失效了。

参考资料

吃得苦中苦,方为人上人

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章           查看所有文章
加:2022-07-21 21:22:54  更:2022-07-21 21:27:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 12:50:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码