Spring事务
概念
Spring事务:事务是一组连续的SQL操作,一组SQL操作都完成后才能提交,如果任何一个环节出现了异常,就会回滚到最初的状态
事务传播方式:其实就是指多个事务存在的时候,Spring如何进行处理,比如一个有事务的方法被另外有事务的方法调用时,这个事务应该如何运行
Spring事务管理
两种方式的事务管理
- 基于 XML 文件方式的声明式事务管理
- 通过 Annotation 注解方式的事务管理
事务管理接口介绍
PlatformTransactionManager
PlatformTransactionManager
接口是 Spring 提供的事务管理器接口,用于管理事务。
Spring将事务的配置详细信息封装到 TransactionDefinition
对象中,然后通过事务管理器的 getTransaction()
方法获得事务的状态(TransactionStatus),并对事务进行下一步的操作
1、获取事务的状态信息
1 | TransactionStatus getTransaction( TransactionDefinition definition) |
2、用于提交事务
1 | void commit(TransactionStatus status) throws TransactionException; |
3、用于回滚事务
1 | void rollback(TransactionStatus status) throws TransactionException; |
TransactionDefinition
TransactionDefinition 接口是事务定义(描述)的对象,它提供了事务相关信息获取的方法
1、获取事务对象名称
1 |
|
2、获取事务传播行为
1 | default int getPropagationBehavior() { |
3、获取事务的超时时间
1 | default int getTimeout() { |
4、获取事务是否已读
1 | default boolean isReadOnly() { |
TransactionStatus
1、获取是否存在保存点
1 | boolean hasSavepoint(); |
2、刷新事务
1 | void flush(); |
3、获取事务是否完成
1 | boolean isCompleted() |
4、获取是否是新事务
1 | boolean isNewTransaction() |
5、获取是否回滚
1 | boolean isRollbackOnly() |
6、设置事务回滚
1 | void setRollbackOnly() |
声明式事务中属性解释
1、name 属性
name=”方法名称|方法名称” 指定受事务控制的方法,支持通配符
2、propagation 属性
propagation=”传播行为名称” 配置事务的传播行为
3、isolation 属性
Isolation=”隔离级别名称” 配置事务的隔离级别
4、readonly 属性
true:开启只读事务。由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段,会对性能有一定提升。建议在查询中开启只读事务
false:不开启只读事务,默认为 false。
5、timeout 属性
timeout 是设置超时属性。以秒为单位。当在限定的时间内不能完成所有操作,就会抛异常
6、rollback-for 属性
rollback-for=”异常名称”
Spring 默认情况下会对 RunTimeException 类型异常进行事务回滚。如果是 Exception 类型的异常则不回滚。
注意:如果异常被 try,catch了,事务就不回滚了,如果想让事务回滚则必须再抛出异常
事务的传播方式
注意:Spring中事务的默认实现使用的是AOP,也就是代理的方式,如果在使用代码测试时,同一个Service类中的方法相互调用需要使用注入的对象来调用,不要直接使用this.方法名来调用,this.方法名调用是对象内部方法调用,不会通过Spring代理,也就是事务不会起作用
注意:事务的传播方式在Propagation枚举类中进行声明,在TransactionDefinition接口中定义了Spring事务的一些属性
首先在没加事务的情况下,调用testA()方法,此时执行了插入数据库a1的操作,然后调用了testB()方法,执行了插入数据库b1的操作,因为抛出异常,导致程序终止,此时数据b2并没有被插入进数据库。出现了不一致情况
REQUIRED(默认传播类型)
如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务
@Transactional(propagation = Propagation.REQUIRED
演示一:
在对testA和testB都加上注解后,当执行testA方法时,因为testA方法上声明了注解,所以在执行testB方法时就加入了testA的事务(当前存在事务,则加入该事务),在testB方法抛出异常后事务会发生回滚,又因为testA和testB使用的是同一个事务,所以事务回滚则说明testA和testB中的操作都会进行回滚,从而避免出现不一致的情况
演示二:
只对testB加上注解,此时执行testA方法,a1数据成功插入到数据库,而b1和b2数据则都没有插入到数据库。
因为在testA调用testB方法时,testB方法创建了事务(如果当前没有事务,则自己新建一个事务),当testB抛出异常时,则只有testB中的操作发生了回滚,也就是b1的存储会发生回滚,但a1数据不会回滚
SUPPORT
当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
@Transactional(propagation = Propagation.SUPPORTS)
演示一:
只对testB加上注解,当执行testA方法时,a1与b1插入到数据库,而b2没有插入到数据库
由于testA没有声明事务,且testB的事务传播行为是SUPPORTS,所以执行testB时就是没有事务的,当执行到testB抛出异常时,就以非事务的方法执行(如果当前没有事务,就以非事务方法执行),所以不会进行回滚
演示二:
当在testA上声明事务且使用REQUIRED传播方式的时候,这个时候执行testB就满足当前存在事务,则加入当前事务,在testB抛出异常时事务就会回滚,最终结果就是a1,b1和b2都不会存储到数据库
MANDATORY
当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常
@Transactional(propagation = Propagation.MANDATORY)
演示一:
只在testB加上注解,此时只有a1插入到数据库成功,b1与b2都插入失败
注意:此处b1与b2插入失败不是因为事务回滚,而是因为testA方法没有声明事务,所以由testA调用的testB方法也没有声明事务,在执行testB方法时就直接抛出事务要求的异常(如果当前事务不存在,则抛出异常),所以也就没有执行插入b1和b2
演示二:
在testA加上默认的REQUIRED注解,testB加上MANDATORY注解,此时a1,b1,b2都插入失败,因为此时testB存在事务(当前存在事务,则加入当前事务),所以回滚时,testA与testB一起回滚
REQUIRES_NEW
创建一个新事务,如果存在当前事务,则挂起该事务。
即:设置事务传播类型为REQUIRES_NEW的方法,在执行时,不论当前是否存在事务,总是会新建一个事务。
演示:
把异常发生的位置换到了testA,然后给testA声明事务,传播类型设置为REQUIRED,testB也声明事务,设置传播类型为REQUIRES_NEW
数据a1没有插入进数据库,而b1和b2插入成功,因为testB的事务传播设置为REQUIRES_NEW,所以在执行testB时会开启一个新的事务,testA中发生的异常时在testA所开启的事务中,所以这个异常不会影响testB的事务提交,testA中的事务会发生回滚,所以最终a1就没有存储,而b1和b2就存储成功了。
NOT_SUPPORTED
在执行时,不论当前是否存在事务,都会以非事务的方式运行
演示:
testA传播类型设置为REQUIRED,testB传播类型设置为NOT_SUPPORTED,且异常抛出位置在testB中
a1和b2没有插入成功,而b1插入成功。testA有事务,而testB不使用事务,所以执行中testB的存储b1成功,然后抛出异常,此时testA检测到异常,事务发生回滚,但是由于testB不在事务中,所以只有testA的存储a1发生了回滚,最终只有b1存储成功,而a1和b1都没有存储
NEVER
不使用事务,如果当前事务存在,则抛出异常
演示:
testA设置传播类型为REQUIRED,testB传播类型设置为NEVER,并且把testB中的抛出异常代码去掉
直接抛出事务异常,且不会有数据存储到数据库。由于testA事务传播类型为REQUIRED,所以testA是运行在事务中,而testB事务传播类型为NEVER,所以当testA调用testB时,testB不会执行而是直接抛出事务异常,此时testA检测到异常就发生了回滚,所以最终数据库不会有数据存入。
NESTED
如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)
对比:
- 和REQUIRES_NEW的区别
REQUIRES_NEW是新建一个事务并且新开启的这个事务与原有事务无关,而NESTED则是当前存在事务时(把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务)
在NESTED情况下父事务回滚时,子事务也会回滚,而在REQUIRES_NEW情况下,原有事务回滚,不会影响新开启的事务
- 和REQUIRED的区别
REQUIRED,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于共用一个事务,所以无论调用方是否catch其异常,事务都会回滚
NESTED,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不受影响
演示一:
testA设置为REQUIRED,testB设置为NESTED,且异常发生在testA中
演示二:
testA设置为REQUIRED,testB设置为NESTED,且异常发生在testB中
a1,a2插入数据库成功,b1和b2插入数据库失败,因为调用方catch了被调方的异常,所以只有子事务回滚了
如果把testB的传播类型改为REQUIRED,结果也就变成了没有数据插入成功。就算在调用方catch了异常,整个事务还是会回滚,因为调用方和被调方共用的同一个事务