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();
}
注意事项
- 事务只能应用到public方法上才会生效。
- 事务需要从外部调用,Spring自调事务会失效。即相同类里边,A方法没有事务,B方法有事务,A方法调用B方法,则B方法的事务会失效,这点尤其重要,因为代理模式只拦截通过代理传入的外部方法调用,所以自动调用事务是不生效的。
- 建议事务注解@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,那事务就从根本上失效了。
- 点赞
- 收藏
- 关注作者
 
             
           
评论(0)