Spring事务

436 阅读6分钟

unnamed.jpg

1、七种传播特性

1.1、PROPAGATION_REQUIRED

解释:默认事务类型,如果没有,就新建一个事务;如果有,就加入当前事务。适合绝大多数情况。关键点:是同一个事务。

场景: 不同的类,class1,class2class1.fun1--->class2.fun2:

  • fun1调用fun2,无论在fun1还是fun2里发生unchecked异常,不论是否catch处理异常,都会触发整个方法的回滚

1.2、PROPAGATION_REQUIRES_NEW

解释:如果没有,就新建一个事务;如果有,就将当前事务挂起.关键点:2个事务是单独的,没有依赖关系

场景:class1.fun1--->class2.fun2: fun1调用fun2

  1. 如果fun2抛出异常且被catch处理,则fun2回滚,fun1不回滚.
  2. 如果fun2抛出异常且没被catch处理,则fun2,fun1都回滚.
  3. 如果fun1抛出异常,则fun1回滚,fun2不回滚. 

1.3、PROPAGATION_NESTED

解释:如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。关键点:2个事务是依赖关系,fun2依赖fun1

场景:class1.fun1--->class2.fun2: fun1调用fun2,

  1. 如果fun2抛出异常且在fun1里catch处理了,则fun2回滚,fun1不回滚, 如果没有catch,则fun1也回滚.
  2. 如果fun1抛出异常,则fun1和fun2都回滚.

1.4、PROPAGATION_MANDATORY

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)这个使用的很少,就不举例子来说了。

1.5、其它三种

若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚,这里不对照案例讲解了,使用的很少。

  1. PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  2. PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  3. PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

2、失效问题

2.1、本类方法调用

当Spring的事务在同一个类时,它的自我调用时事务将失效.

2.1.1、现象:

  1. ServiceA类为Web层的Action服务
  2. Action调用了ServiceA的方法A,而方法A没有声明事务(原因是方法A本身比较耗时而又不需要事务)
  3. ServiceA的方法A调用了自己的方法B,而方法B声明了事务,但是方法B的事务声明在这种情况失效了。
  4. 如果在方法A上也声明事务,则在Action调用方法A时,事务生效,而方法B则自动参与了这个事务。 

2.1.2、分析原因

因为他们是本类方法直接调用,这个时候会用this关键字,没有经过 Spring 的代理类去调用此方法,从而没有开启事务管理,默认只有在外部调用事务才会生效。 

2.1.3、解决办法

  • 最直白的就是把方法拆出来,放在两个类里面(Spring推荐的一种方式);

  • 在类里面注入自己,用注入的对象再调用另外一个方法,这个不太优雅;

  • 在Spring的配置里面增加一段配置:<aop:aspectj-autoproxy expose-proxy="true"/>

  • 在方法里使用编程式事务

2.2、异常抛出类型错误

2.2.1、现象:

通过日志查看,回滚失败

2.2.2、分析原因

@Transactional 注解作用于类上时,该类的所有public方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。

@Transactional 注解中如果不配置rollbackFor属性,那么事务在遇到RuntimeExceptionError的时候会回滚,当加上 rollbackFor=Exception.class时,那么事务在遇到Error时不再回滚。

image.png

2.2.3、@Transactional 的常用配置参数总结

属性名说明
propagation事务的传播行为,默认值为 REQUIRED,可选的值在上面介绍过
isolation事务的隔离级别,默认值采用 DEFAULT,可选的值在上面介绍过
timeout事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly指定事务是否为只读事务,默认值为 false。
rollbackFor用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。

2.3、方法不是public的

Spring 通过动态代理实现事务管理时,一般使用基于 JDK 动态代理和 CGLIB(Code Generation Library)两种方式,具体的选择取决于被代理的类是否实现了接口。而我们写的代码service一般都是接口,所以会选用JDK 动态代理。

JDK 动态代理: JDK 动态代理是基于接口的代理,它要求被代理的类必须实现至少一个接口。生成的代理对象实现了被代理接口,并且代理的方法与接口中的方法一致。由于接口中的方法默认是 public 的,因此使用 JDK 动态代理时,只能代理 public 方法。

CGLIB 动态代理: CGLIB 动态代理则不依赖于接口,它通过生成目标类的子类来创建代理对象。生成的子类覆盖了目标类中的方法,包括 protectedpublic 方法,但不包括 private 方法。所以,CGLIB 动态代理能够代理除了 private 方法之外的其他方法,包括 protectedpublic 方法。

2.4、其它失效的场景

  1. 不是由Spring管理的Bean;
  2. 数据源未配置事务管理器;
  3. 数据库引擎不支持事务,比如MySQL,其MyISAM引擎是不支持事务操作的,InnoDB才是支持事务的引擎,一般要支持事务都会使用InnoDB;
  4. 事务传播机制设置以不支持事务运行;
  5. 异常被catch掉了;

3、总结

b0db60f03f52526d29686c904d6b001f.jpeg

  1. @Transactional 注解只有作用到 public 方法上事务才生效,不推荐在接口上使用;
  2. 避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效;
  3. 正确的设置 @TransactionalrollbackForpropagation 属性,否则事务可能会回滚失败;
  4. @Transactional 注解的方法所在的类必须被 Spring 管理,否则不生效;
  5. 底层使用的数据库必须支持事务机制,否则不生效;