Spring事务

举报
别团等shy哥发育 发表于 2023/10/20 16:51:52 2023/10/20
【摘要】 Spring事务 Spring管理事务的方式有几种?编程式事务:在代码中硬编码,通过TransactionTemplate或者TransactionManager手动管理事务,实际应用中很少使用,便于理解Spring事务管理的原理。声明式事务:在XML配置文件中配置或者直接基于注解(推荐),实际是通过AOP实现(基于@Transaction的全注解方式使用最多)。 Spring事务中有哪几...

Spring事务

Spring管理事务的方式有几种?

  • 编程式事务:在代码中硬编码,通过TransactionTemplate或者TransactionManager手动管理事务,实际应用中很少使用,便于理解Spring事务管理的原理。
  • 声明式事务:在XML配置文件中配置或者直接基于注解(推荐),实际是通过AOP实现(基于@Transaction的全注解方式使用最多)。

Spring事务中有哪几种事务传播行为?

事务传播是为了解决业务层方法之间互相调用的事务问题。

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

正确的事务传播行为可能的值如下(都是TransactionDefinition接口中的常量):

  • PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务(使用最多的一个事务传播行为,我们平时经常使用的@Transaction注解默认使用就是这个事务传播行为)。
  • PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  • PROPAGATION_NESTED:如果存在当前事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED
  • PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

若是错误的配置以下3种事务传播行为,事务将不会发生回滚:

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

这里Propagation是个枚举类:

public enum Propagation {
   REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
   SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
   MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
   REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
   NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
   NEVER(TransactionDefinition.PROPAGATION_NEVER),
   NESTED(TransactionDefinition.PROPAGATION_NESTED);
   private final int value;
   Propagation(int value) {
      this.value = value;
   }
   public int value() {
      return this.value;
   }
}

Spring事务隔离级别有哪几种?

这块Spring也定义了一个枚举类:Isolation

public enum Isolation {
   DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
   READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
   READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
   REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
   SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
   private final int value;
   Isolation(int value) {
      this.value = value;
   }
   public int value() {
      return this.value;
   }
}
  • ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,MySQL默认采用的REPEATABLE_READ隔离级别,Oracle默认采用的READ_COMMITTED隔离级别。

  • ISOLATION_READ_UNCOMMITTED:最低的隔离级别,使用这个隔离级别很少,因为它允许尚未提交的数据变更,可能会导致脏读、幻读和不可重复读。

  • ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读和不可重复读仍有可能发生。

  • ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

  • ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读和幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

@Transactional(rollbackFor=Exception.class)注解

Exception分为运行时异常(RuntimeException)和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。

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

@Transactional注解中不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事务遇到非运行时异常时候也会滚。

回滚规则

默认情况下,事务只有遇到运行时异常(RuntimeException的子类)以及Error时才会回滚,在遇到检查型异常(Checked Exception)时不会回滚。

像1/0,空指针这些是RuntimeException,而IOException则算是Checked Exception,换言之,默认情况下,如果发生IOException并不会导致事务回滚。

如果希望在发生IOException时也能触发事务回滚,那么可以按照如下方式配置:

@Transactional(rollbackFor = IOException.class)
public void handle2() {
    jdbcTemplate.update("update user set money = ? where username=?;", 1, "zhangsan");
    accountService.handle1();
}

注意事项

  1. 事务只能应用到public方法上才会生效。
  2. 事务需要从外部调用,Spring自调事务会失效。即相同类里边,A方法没有事务,B方法有事务,A方法调用B方法,则B方法的事务会失效,这点尤其重要,因为代理模式只拦截通过代理传入的外部方法调用,所以自动调用事务是不生效的。
  3. 建议事务注解@Transactional一般添加在实现类上,而不要定义在接口上,如果加在接口类或接口方法上时,只有配置基于接口的代理这个注解才会生效。

@Transactional注解失效场景(*)

1.@Transactional应用在非public修饰的方法上

事务依赖于AOP动态代理实现,非public不会生效

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

比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而方法B有。则外部调用方法A之后,方法B的事务是不会起作用的。

只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。

3.@Transactional注解属性propagation设置错误

这种失效是由于配置错误,若是错误的配置以下三种propagation,事务将不会发生回滚。

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

4.@Transactional注解属性rollbackFor设置错误

默认情况下,事务只有遇到运行时异常(RuntimeException的子类)以及Error时才会回滚,在遇到检查型异常(Checked Exception)时不会回滚。比如IOException这种,需要自己指定。可以直接写成@Transactional(rollbackFor=Exception.class)

5.手动catch异常,导致@Transactional失效

非要catch一定要抛出throw new RuntimeException()

注解中指定抛出的异常类型@Transactional(rollbackFor=Exception.class)

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

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

6.数据库引擎不支持事务

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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。