Spring的@Transactional失效场景和事务注解方法之间调用(一)

Spring的@Transactional失效场景和事务注解方法之间调用(一)

一 spring声明式事务的原理

1.1 原理概述

image-20230526172356437

spring的声明式事务@Transactional依赖springAOP和数据库事务实现,是对注解的方法所在的类,生成代理类,在执行方法点作为pointcut,执行around的环式增强。主题流程如上。

spring声明式事务的原理和实现关键如下:

image-20220518194538939

spring事务的实现是基于数据库事务和AOP实现的;spring事务的隔离级别由数据库实现;spring事务的传播行为由spring实现,传播行为,定义了事务与子事务获取连接、事务提交、回滚的具体方式。

spring事务是基于AOP的环绕通知around实现,在拦截业务方法时,在执行业务前获取DataSource的connection开启事务;在执行业务方法后,根据执行成功与否,选择执行commit或者rollback。

1.2 区别@Transactional失效和@Transactional注解的方法之间调用

此处需要注意两者的差别:

@Transactional的失效,是指当前注解不能实现声明式事务,该注解无效;但是在方法之间调用时,例如@Transactional的A调用B,即便B被注解过,虽然B的注解失效,但是A->B作为整体的事务,仍旧可能会作为一个完整的事务,能够同时commit或者rollback。

1.3 事务的传播机制

image-20230526173422229

1 对于spring的@Transactional,默认的传播级别是PROPAGATION_REQUIRED---支持当前事务,如果存在事务,则加入;如果不存在,就加入当前事务。

2 对于在@Transactional注解的方法之间调用时,如果出现不支持事务的传播机制,会报错。never、not-supported、supports,这三种事务会导致@transactional失效。

1.4 @Transactional的重要参数

此外,@Transactional注解的重要参数如下:

image-20230528114954861

1.5 @Transactional使用

(通常在开发时,在业务开发过程,一般是controller调用service层的方法,然后service层由于是interface和实现类的结构,因此对service具体服务,用@Transactional标注,service中调用其他相关的方法---会有同类调用事务方法和不同类调用事务方法)

  1. 只能注解在public方法。如果标记非public,如static、private、final时,不会报错,但是没有事务功能;

  2. 该注解默认会对不可查unchecked异常回滚(包括runtimeException和error),而遇到受检查的exception(checked异常,就是非运行期时抛出的异常,编译器会在编译前检查出异常并抛出)默认不会回滚,如果需要让其回滚,需要@Transactional(rollbackFor=异常);

    如果不想让unchecked异常回滚,此时@Transactional(notRollbackFor=异常);

    image-20230528115625319
  3. @Transactional可以使用在接口、接口方法、类、类方法上。但是其中会各有各的问题:

    3.1 如果使用在接口或者接口方法上:(由于注解不可继承)

    a:如果当前使用jdk动态代理时,那么会生效;

    b:如果使用基于类的动态代理(cglib)时,那么由于注解不会被继承,所以事务会无效;

    3.2 如果使用在类上,此时会使得所有的public方法都是事务方法,会影响性能。如果非要此种操作:

    @Transactional
    public class Order{
    
    public void methodA(){
    }
    
    //不需要事务的方法
    @Transactional(propagation=Propagation.NOT_SUPPOREDE)
    public void methodB(){
    }
    }
    
  4. @Transactional注解的方法自调用问题--类中的事务方法A,调用本身的事务方法B。

    详细见第三章节。

1.6 @Transactional的回滚与异常、try/catch处理

image-20230528123230479

spring的注解事务,默认会对不可查事务回滚(runtimeException和error),而默认不会对非运行时异常回滚。

spring的事务的开始,是在调用业务方法(被@Transactional注解的方法)之前开始,业务方法执行完毕后根据是否有异常throw,来决定commit还是rollback。

而对于代码中有try-catch的情况,那么try里面的方法就脱离了事务管理,此时出现异常会被catch,即便出现异常也不会回滚。如果需要事务在此时生效,此时有两种操作:

方法一:catch中throw new RuntimeException ("xxxxxx"); //推荐
方法二:catch中强制设置异常,手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

此外,关于try-catch操作,还需要注意methodB中@Transactional设置事务传播机制为requires和require-new的情况:

1.7 @Transactional的自我调用及解决方法

image-20230529104758881

对于在同类中调用方法,由于事务的管理通过代理对象的执行,才生效。所以事务方法A调用事务方法B,方法B上的事务注解,就不会生效。

那么想要实现事务方法自调用依然有效的方案,有如下几种:

1 抽离方法构造新类

将需要调用本类中的方法methodB,单独抽离出来,分别创建interface类和实现类,在方法B加注解,再实现类之间的调用。但是此种过程会增加代码量和剥离出新的类,基本不使用;

2 使用AopContext.currentProxy()

使用AopContext.currentProxy()获取到本类的代理对象,然后再去调用对应的方法。由于使用CGLIB的关于类的代理,因此需要再启动类上添加注解@EnableAspectJAutoProxy(exposeProxy = true) 来开启AOP。

@Transactional
public void methodA(){
((UserService)AopContext.currentProxy()).methodB();
}

3 当前类注入自身

在methodA中调用methodB时,使用注入的对象调用自己

@Autowired
UserService userService;

@Transactional
public void methodA(){
userService.methodB();
}

4 AspectJ和SpringAOP

spring的自调用失效和只能适用于public,是由于使用SpringAOP导致,为了彻底解决该问题,可以使用AspectJ代替SpringAOP代理。

image-20230529125507828

image-20230529130144353

image-20230529130159034

实现办法:

需要将如下AspectJ信息添加到xml中

<tx:annotation-driven mode="aspectj" />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean
class="org.springframework.transaction.aspectj.AnnotationTransactionAspect"
factory-method="aspectOf">
<property name="transactionManager" ref="transactionManager" />
</bean>

javaConfig信息

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ, proxyTargetClass = true)
public class Application {
}

pom文件中添加

      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aspects</artifactId>
          <version>4.3.2.RELEASE</version>
      </dependency>
      <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjrt</artifactId>
          <version>1.8.9</version>
      </dependency>
          <plugin>
              <groupId>org.codehaus.mojo</groupId>
              <artifactId>aspectj-maven-plugin</artifactId>
              <version>1.9</version>
              <configuration>
                  <showWeaveInfo>true</showWeaveInfo>
                  <aspectLibraries>
                      <aspectLibrary>
                          <groupId>org.springframework</groupId>
                          <artifactId>spring-aspects</artifactId>
                      </aspectLibrary>
                  </aspectLibraries>
              </configuration>
              <executions>
                  <execution>
                      <goals>
                          <goal>compile</goal>
                          <goal>test-compile</goal>
                      </goals>
                  </execution>
              </executions>
          </plugin>

二 @Transactional失效的场景

image-20230526180220201

根于spring事务的实现原理,将@Transactional的失效分为如下的类型:

代理不生效、框架不支持的功能、@Transactional注解的错误使用。

2.1 代理不生效导致

spring事务的实现,依赖于对于目标方法对应的目标类,生成代理对象。如果目标类无法被spring代理,那么将无法使用spring进行事务的创建与管理。

Spring生成代理两种方式:

a 基于接口的JDK动态代理,此时要求被注解的方法需要实现interface,才能够被代理;

b 基于创建目标类的子类,完成CGLIB的代理模式。(该种模式会创建新的子类)

1 @Transactional注解在interface方法

注解支持标注在类和方法上,如果注解在interface的方法上,且代理方式如果是CGLIB,那么目标类的代理对象会通过生成子类实现(子类是组装字节实现),此时对于新的代理类,执行过程中,就无法解析到@Transactional注解(新子类的方法上没有注解)。

总结:

Transactional注解在类或者接口上,会对当前所有的public方法都有效。此时,可能有的方法不需要事务,会影响性能;其次,不同方法对于事务的参数要求不同。所以,一般建议注解在方法上。

2 @Transactional注解final、static修饰的类或者方法

当修饰final、static的类或者方法,其不会被子类继承和实现,此时如果CGLIB实现代理创建,那么就不会有该方法的实现,就不会调用事务。

因此,@transactional需要注解在public上,才能够被代理。

3 @Transactional注解的方法自身调用

image-20230528125228129

事务的管理通过代理的方法才生效,而如果是方法内部调用,methodA-->methodB,此时methodB不会走代理的逻辑,此时注解在b上的注解就失效了。

2.2 框架(源码)不支持的功能

1 注解在非public方法上

spring事务不支持对非public的方法进行事务管理;

2 多线程调用

3 数据库本身不支持事务

mysql的myisam引擎不支持事务;只有innodb存储引擎支持事务。

2.3 错误使用@Transactional

1 传播机制设置错误

image-20230529134044032

其中三种传播机制never、notsupported、supports不支持事务,因此在抛异常时,不会回滚;

2 rollbackFor设置错误

image-20230529134436010

默认只回滚error和运行时异常,而对于如IOExeception不会回滚。

可以指定@Transactional(rollbackFor=Exceptin.class)方式,进行全局异常的捕获和回滚。

img

3 异常被try-catch捕获

对于classA.methodA调用classB.methoddB方法中,默认执行required的传播机制,会加入当前事务中。如果B抛异常,此时会标记事务为rollback-only,而如果a方法中try-catch掉异常,a中便不会抛异常,spring认为a方法需要commit,此时系统会抛异常

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

因此,需要再catch中再次抛异常。

4 嵌套事务???

对于a调用b,需要只rollback其b方法,而不回滚a方法,有两种操作:

1 b方法设置required-new,重新开启事务;

2 在b方法中try-catch。

classA{
  
@Transactional
methodA(){
try{
classB.methodB()
}catch

}
} //classA

总结

image-20230529135351971

posted @ 2023-06-12 18:26  LeasonXue  阅读(683)  评论(0)    收藏  举报