Spring事务传播行为
Spring声明事务事务传播行为
名词解释
事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
- TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
区别解释
上文可以看出PROPAGATION_SUPPORTS,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER,PROPAGATION_MANDATORY解释比较明确,一般不会出现歧义
重点解释下PROPAGATION_REQUIRED和PROPAGATION_REQUIRES_NEW及PROPAGATION_NESTED的区别.
为了解释方便,假设一下代码为场景,该案例只是表示调用场景,因为缺乏必要的Spring注解和调用方式的不正确实际事务不会生效,具体使用案例参照下文案例.
public void methodA(){
methodB();
// do something
}
public void methodB(){
// do something
}
方法A为调用方,方法B为被调用方
PROPAGATION_REQUIRED
Spring默认的事务传播行为,如果调用方A有事务,被调用方B就加入这个事务,如果调用方A没有事务,被调用方B就新建一个事务.如果加入的话,调用方A和被调用方B是在完全相同的一个事务中,共同回滚,提交.
PROPAGATION_REQUIRES_NEW
无论当前调用方A是否存在事务,被调用方B都新建一个事务,如果调用方A存在事务,则挂起.调用方A和被调用方B是完全不同的两个事务.两个事务分别回滚,提交
PROPAGATION_NESTED
如果调用方A存在事务,被调用方B创建一个嵌套事务来运行,如果调用方A不存在事务,被调用方B新建一个事务,所谓嵌套运行可以理解为,调用方A回滚会携带被调用方B一起回滚,被调用方B回滚不会影响调用方A.
--调用方A开始(设置savePointA)
----被调用方B开始(设置savePointB)
----被调用方B结束(失败则回滚到savePointB)
--调用方A结束(成功则提交,失败则回滚到savePointA)
可以看出,
B想提交成功需要两个条件,B成功,A也成功
B想要回滚,A或者B有一个异常就可以了,
A提交只需要A中成功就可以(B抛出的异常需要处理),A异常的时候会共同回滚
A异常会回滚到savePointA,B异常只回滚到savePointB
上文只是本人的理解,Spring真正的实现方式是怎样没有深入了解,以后有机会会补上,有懂的请不吝赐教.
代码验证(举例)
上面解释也许只是对着名词解释假设的,具体是不是这样还需要验证一番
@Service
public class StudentServiceImpl implements StudentService {
@Resource
StudentMapper studentMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public void methodFather() {
int insert = studentMapper.insert(Student.builder().name("父方法先").build());
try {
((StudentServiceImpl) AopContext.currentProxy()).method_NESTED();
} catch (Exception e) {
}
try {
((StudentServiceImpl) AopContext.currentProxy()).method_REQUIRES_NEW();
} catch (Exception e) {
}
try {
((StudentServiceImpl) AopContext.currentProxy()).method_REQUIRED();
} catch (Exception e) {
}
int insert2 = studentMapper.insert(Student.builder().name("父方法后").build());
}
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
public void method_NESTED() {
int insert = studentMapper.insert(Student.builder().name("子方法NESTED").build());
}
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void method_REQUIRES_NEW() {
int insert = studentMapper.insert(Student.builder().name("子方法REQUIRES_NEW").build());
}
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void method_REQUIRED() {
int insert = studentMapper.insert(Student.builder().name("子方法REQUIRED").build());
}
}
上面的代码可以看出,调用方methodFather分别调用了method_NESTED,method_REQUIRES_NEW和method_REQUIRED三个呗调用方,调用方法的地方都分别使用了trycatch语句,保证子方法的异常不会影响到父方法.
正常运行

Creating a new SqlSession
Registering transaction synchronization for SqlSession
**注册父事务a1b7bb8**
[org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@192cd917] will be managed by Spring
==> Preparing: INSERT INTO student ( name ) VALUES ( ? )
==> Parameters: 父方法先(String)
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8] from current transaction
==> Preparing: INSERT INTO student ( name ) VALUES ( ? )
==> Parameters: 子方法NESTED(String)
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Transaction synchronization suspending SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Creating a new SqlSession
**注册子事务REQUIRES_NEW a1b7bb8**
Registering transaction synchronization for SqlSession
[org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@16831f94] will be managed by Spring
==> Preparing: INSERT INTO student ( name ) VALUES ( ? )
==> Parameters: 子方法REQUIRES_NEW(String)
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
Transaction synchronization resuming SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8] from current transaction
==> Preparing: INSERT INTO student ( name ) VALUES ( ? )
==> Parameters: 子方法REQUIRED(String)
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8] from current transaction
==> Preparing: INSERT INTO student ( name ) VALUES ( ? )
==> Parameters: 父方法后(String)
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
可以看到只有REQUIRES_NEW新建了一个事务,其他都是使用的父事务
父事务异常
将父方法methodFather修改为如下
@Override
@Transactional(rollbackFor = Exception.class)
public void methodFather() {
int insert = studentMapper.insert(Student.builder().name("父方法先").build());
try {
((StudentServiceImpl) AopContext.currentProxy()).method_NESTED();
} catch (Exception e) {
}
try {
((StudentServiceImpl) AopContext.currentProxy()).method_REQUIRES_NEW();
} catch (Exception e) {
}
try {
((StudentServiceImpl) AopContext.currentProxy()).method_REQUIRED();
} catch (Exception e) {
}
int insert2 = studentMapper.insert(Student.builder().name("父方法后").build());
// 父事务异常
int i = 1 / 0;
}
运行结果

父事务异常,回滚,加入父事务的REQUIRED和嵌入的NESTED都回滚了,只有开启新事务的REQUIRES_NEW不受影响,成功提交了
REQUIRED子方法异常
去除父方法异常代码
修改method_REQUIRED代码如下
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void method_REQUIRED() {
int insert = studentMapper.insert(Student.builder().name("子方法REQUIRED").build());
//REQUIRED异常测试
int i = 1 / 0;
}

结果与父方法异常相同,原因是REQUIRED是加入父事务,它回滚等于父事务回滚
REQUIRES_NEW异常
相同方法,去除REQUIRED中的异常
REQUIRES_NEW子方法加入异常
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void method_REQUIRES_NEW() {
int insert = studentMapper.insert(Student.builder().name("子方法REQUIRES_NEW").build());
//REQUIRES_NEW异常测试
int i = 1 / 0;
}

除了REQUIRES_NEW异常自己回滚了,其他成功提交
NESTED异常测试
相同方法,去除其他异常
method_NESTED中加入异常
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
public void method_NESTED() {
int insert = studentMapper.insert(Student.builder().name("子方法NESTED").build());
//NESTED异常测试
int i = 1 / 0;
}
因为测试使用的是同一张表,该异常出现的时候会进行锁表,造成REQUIRES_NEW的提交超时,此处将method_NESTED方法调用移到最后执行

可以看到NESTED作为嵌套事务自己回滚了,不影响父事务提交

浙公网安备 33010602011771号