Skip to content

《面渣逆袭》Spring 篇 · 第 4/7 章。原版 PDF(下载 / 打印)

Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,Spring 是无法提供事务功能的。Spring 只提供统一事务管理交叉,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过数据库自己的事务机制实现。

23.Spring 事务的种类?

Spring 支持编程式事务管理和声明式事务管理两种方式:

配图

  1. 编程式事务

编程式事务管理使用 TransactionTemplate ,需要显式执行事务。

  1. 声明式事务

  2. 声明式事务管理建立在 AOP 之上的。其本质是通过 AOP 功能,对方法前后进行拦截,将事务

处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务

  1. 优点是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明

或通过 @ Transactional 注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

24.Spring 的事务隔离级别?

Spring 的交叉 TransactionDefinition 中定义了表示隔离级别的常量,当然其实主要还是对应数据库的事务隔离级别:

  1. ISOLATION_DEFAULT :使用后端数据库默认的隔离界别,MySQL 默认可重复读,Oracle 默

认读已提交。

  1. ISOLATION_READ_UNCOMMITTED :读未提交

  2. ISOLATION_READ_COMMITTED :读已提交

  3. ISOLATION_REPE ATA BLE_READ :可重复读

  4. ISOLATION_SERIALIZABLE :串行化

25.Spring 的事务传播机制?

Spring 事务的传播机制说的是,当多个事务同时存在的时候— — 一般指的是多个事务方法相互调用时,Spring 如何处理这些事务的行为。

事务传播机制是使用简单的 ThreadLocal 实现的,所以,如果调用的方法是在新线程调用的,事务传播实际上是会失效的。

配图

Spring 默认的事务传播行为是 PROPA FAT ION_REQUIRED ,它适合绝大多数情况,如果多个

ServiceX#methodX()都工作在事务环境下(均被 Spring 事务增强),且程序中存在调用链
Service1#method1()->Service2#method2()->Service3#method3() ,那么这 3 个服务类的三个方法

通过 Spring 的事务传播机制都工作在同一个事务中。

26.声明式事务实现原理了解吗?

就是通过 AOP/ 动态代理。

在 Bean 初始化阶段创建代理对象:Spring 容器在初始化每个单例 bean 的时候,会遍历容器中的所有 BeanPostProcessor 实现类,并执行其 postProcessAfterInitialization 方法,在执行

AbstractAutoProxyCreator 类的 postProcessAfterInitialization 方法时会遍历容器中所有的切面,查找与当前实例化 bean 匹配的切面,这里会获取事务属性切面,查找@ Transactional 注解及其属性值,然后根据得到的切面创建一个代理对象,默认是使用 JDK 动态代理创建代理,如果目标类是交叉,则使用 JDK 动态代理,否则使用 Cglib 。

在执行目标方法时进行事务增强操作:当通过代理对象调用 Bean 方法的时候,会触发对应的

AOP 增强拦截器,声明式事务是一种环绕增强,对应交叉为MethodInterceptor,事务增强对该交叉的实现为TransactionInterceptor,类图如下:

配图

事务拦截器TransactionInterceptorinvoke方法中,通过调用父类TransactionAspectSupport

invokeWithinTransaction方法进行事务处理,包括开启事务、事务提交、异常回滚。

27.声明式事务在哪些情况下会失效?

配图

java
1@Transactional 应用在非 public 修饰的方法上
如果 Transactional 注解应用在非 public 修饰的方法上,Transactional 将会失效。

是因为在 SpringAOP 代理时,Tr ansactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedIntercepto r(CglibAopProxy 的内部类)的 intercept 方法 或

JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的computeTransactionAttribute方法,获取 Transactional 注解的事务配置信息。

java
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 的属性配置信息。

2 、@ Transactional 注解属性 propagation 设置错误

TransactionDefinition.PROPAGATION_SUPPORT S:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

TransactionDefinition.PROPAGATION_NOT_SUPPORTE D:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

TransactionDefinition.PROPAGATION_NEVE R:以非事务方式运行,如果当前存在事务,则抛出异常。

3 、@ Transactional 注解属性 rollbackFor 设置错误

rollbackFor 可以指定能够触发事务回滚的异常类型。Spring 默认抛出了未检查 unchecked 异常(继承自 RuntimeException 的异常)或者 Error 才回滚事务,其他异常不会触发回滚事务。

配图

// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class

若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。

4 、同一个类中方法调用,导致@ Transactional 失效

开发中避免不了会对同一个类里面的方法调用,比如有一个类 Test ,它的一个方法 A ,A 再调用本类的方法 B (不论方法 B 是用 public 还是 private 修饰),但方法 A 没有声明注解事务,而 B 方法有。则外部调用方法 A 之后,方法 B 的事务是不会起作用的。这也是经常犯错误的一个地方。

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

java
 //@Transactional
     @GetMapping("/test")
     private Integer A() throws Exception {
         CityInfoDict cityInfoDict = new CityInfoDict();
         cityInfoDict.setCityName("2");

/

B 插入字段为 3 的数据/

         this.insertB();

/

A 插入字段为 2 的数据/

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

这种情况是最常见的一种@ Transactional 注解失效场景

@ Transactional

java
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 方法此时 trycatch 了 B 方法的异常,那这个事务就不能正常回滚了,会抛出异常:

org.springframework.transaction.UnexpectedRollbackException:Transactionrolledback

becauseithasbeenmarkedasrollback-only

本文整理自三分恶《面渣逆袭》系列的公开内容,仅供个人学习使用

本站仅供个人学习使用,请勿外传