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
的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit
orrollback
,事务是否执行取决于是否抛出runtime异常
。如果抛出RuntimeException
并在你的业务方法中没有catch到的话,事务会回滚。
在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException()
,或者注解中指定抛异常类型**@Transactional(rollbackFor=Exception.class)
**,否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。
6.数据库引擎不支持事务
这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb
引擎。一旦数据库引擎切换成不支持事务的MyIASM
,那事务就从根本上失效了。
- 点赞
- 收藏
- 关注作者
评论(0)