概念

Spring事务:事务是一组连续的SQL操作,一组SQL操作都完成后才能提交,如果任何一个环节出现了异常,就会回滚到最初的状态

事务传播方式:其实就是指多个事务存在的时候,Spring如何进行处理,比如一个有事务的方法被另外有事务的方法调用时,这个事务应该如何运行


Spring事务管理

两种方式的事务管理

  • 基于 XML 文件方式的声明式事务管理
  • 通过 Annotation 注解方式的事务管理

事务管理接口介绍

PlatformTransactionManager

PlatformTransactionManager 接口是 Spring 提供的事务管理器接口,用于管理事务。

Spring将事务的配置详细信息封装到 TransactionDefinition 对象中,然后通过事务管理器的 getTransaction() 方法获得事务的状态(TransactionStatus),并对事务进行下一步的操作

1、获取事务的状态信息

1
2
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;

2、用于提交事务

1
void commit(TransactionStatus status) throws TransactionException;

3、用于回滚事务

1
void rollback(TransactionStatus status) throws TransactionException;

TransactionDefinition

TransactionDefinition 接口是事务定义(描述)的对象,它提供了事务相关信息获取的方法

1、获取事务对象名称

1
2
3
4
@Nullable
default String getName() {
return null;
}

2、获取事务传播行为

1
2
3
4
default int getPropagationBehavior() {
return PROPAGATION_REQUIRED;
}
// PROPAGATION_REQUIRED = 0

3、获取事务的超时时间

1
2
3
4
default int getTimeout() {
return TIMEOUT_DEFAULT;
}
// TIMEOUT_DEFAULT = -1

4、获取事务是否已读

1
2
3
default boolean isReadOnly() {
return false;
}

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并没有被插入进数据库。出现了不一致情况

image-20221015205500104

REQUIRED(默认传播类型)

如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务

@Transactional(propagation = Propagation.REQUIRED

演示一:

在对testA和testB都加上注解后,当执行testA方法时,因为testA方法上声明了注解,所以在执行testB方法时就加入了testA的事务(当前存在事务,则加入该事务),在testB方法抛出异常后事务会发生回滚,又因为testA和testB使用的是同一个事务,所以事务回滚则说明testA和testB中的操作都会进行回滚,从而避免出现不一致的情况

image-20221015210148668

演示二:

只对testB加上注解,此时执行testA方法,a1数据成功插入到数据库,而b1和b2数据则都没有插入到数据库。

因为在testA调用testB方法时,testB方法创建了事务(如果当前没有事务,则自己新建一个事务),当testB抛出异常时,则只有testB中的操作发生了回滚,也就是b1的存储会发生回滚,但a1数据不会回滚

image-20221015211019078

SUPPORT

当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行

@Transactional(propagation = Propagation.SUPPORTS)

演示一:

只对testB加上注解,当执行testA方法时,a1与b1插入到数据库,而b2没有插入到数据库

由于testA没有声明事务,且testB的事务传播行为是SUPPORTS,所以执行testB时就是没有事务的,当执行到testB抛出异常时,就以非事务的方法执行(如果当前没有事务,就以非事务方法执行),所以不会进行回滚

image-20221015211533654

演示二:

当在testA上声明事务且使用REQUIRED传播方式的时候,这个时候执行testB就满足当前存在事务,则加入当前事务,在testB抛出异常时事务就会回滚,最终结果就是a1,b1和b2都不会存储到数据库

image-20221015212119442

MANDATORY

当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常

@Transactional(propagation = Propagation.MANDATORY)

演示一:

只在testB加上注解,此时只有a1插入到数据库成功,b1与b2都插入失败

注意:此处b1与b2插入失败不是因为事务回滚,而是因为testA方法没有声明事务,所以由testA调用的testB方法也没有声明事务,在执行testB方法时就直接抛出事务要求的异常(如果当前事务不存在,则抛出异常),所以也就没有执行插入b1和b2

image-20221015212403945

演示二:

在testA加上默认的REQUIRED注解,testB加上MANDATORY注解,此时a1,b1,b2都插入失败,因为此时testB存在事务(当前存在事务,则加入当前事务),所以回滚时,testA与testB一起回滚

image-20221015213053129

REQUIRES_NEW

创建一个新事务,如果存在当前事务,则挂起该事务。

即:设置事务传播类型为REQUIRES_NEW的方法,在执行时,不论当前是否存在事务,总是会新建一个事务。

演示:

把异常发生的位置换到了testA,然后给testA声明事务,传播类型设置为REQUIRED,testB也声明事务,设置传播类型为REQUIRES_NEW

数据a1没有插入进数据库,而b1和b2插入成功,因为testB的事务传播设置为REQUIRES_NEW,所以在执行testB时会开启一个新的事务,testA中发生的异常时在testA所开启的事务中,所以这个异常不会影响testB的事务提交,testA中的事务会发生回滚,所以最终a1就没有存储,而b1和b2就存储成功了。

image-20221015223733222

NOT_SUPPORTED

在执行时,不论当前是否存在事务,都会以非事务的方式运行

演示:

testA传播类型设置为REQUIRED,testB传播类型设置为NOT_SUPPORTED,且异常抛出位置在testB中

a1和b2没有插入成功,而b1插入成功。testA有事务,而testB不使用事务,所以执行中testB的存储b1成功,然后抛出异常,此时testA检测到异常,事务发生回滚,但是由于testB不在事务中,所以只有testA的存储a1发生了回滚,最终只有b1存储成功,而a1和b1都没有存储

image-20221015224859776

NEVER

不使用事务,如果当前事务存在,则抛出异常

演示:

testA设置传播类型为REQUIRED,testB传播类型设置为NEVER,并且把testB中的抛出异常代码去掉

直接抛出事务异常,且不会有数据存储到数据库。由于testA事务传播类型为REQUIRED,所以testA是运行在事务中,而testB事务传播类型为NEVER,所以当testA调用testB时,testB不会执行而是直接抛出事务异常,此时testA检测到异常就发生了回滚,所以最终数据库不会有数据存入。

image-20221015225322618

NESTED

如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)

对比:

  • 和REQUIRES_NEW的区别

REQUIRES_NEW是新建一个事务并且新开启的这个事务与原有事务无关,而NESTED则是当前存在事务时(把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务)
在NESTED情况下父事务回滚时,子事务也会回滚,而在REQUIRES_NEW情况下,原有事务回滚,不会影响新开启的事务

  • 和REQUIRED的区别

REQUIRED,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于共用一个事务,所以无论调用方是否catch其异常,事务都会回滚
NESTED,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不受影响

演示一:

testA设置为REQUIRED,testB设置为NESTED,且异常发生在testA中

image-20221015230118493

演示二:

testA设置为REQUIRED,testB设置为NESTED,且异常发生在testB中

a1,a2插入数据库成功,b1和b2插入数据库失败,因为调用方catch了被调方的异常,所以只有子事务回滚了

如果把testB的传播类型改为REQUIRED,结果也就变成了没有数据插入成功。就算在调用方catch了异常,整个事务还是会回滚,因为调用方和被调方共用的同一个事务

image-20221015230849570