@Transactional注解的事务传播行为
Transactional是Spring事务管理提供的注解,在一个方法中加上了这个注解,那么这个方法就将是有事务的,Transactional.propagation:传播行为
1.Transactional注解中七种事务传播行为
1.它是用来表示当一个事务传播行为修饰的方法被另一个方法调用时,事务如何进行传播
事务传播行为 效果
REQUIRED:(必须) 如果以前有事务,就和之前的事务共用一个事务,没有就创建一个事务。
REQUIRES_NEW(新的事务) 创建一个新的事务,如果以前有事务,暂停前面的事务,也就是说总是用新事务。
SUPPORTS(支持) 之前有事务,就和之前事务共用的方式运行,没有事务也可以。 MANDATORY(强制) 一定要有事务,如果没事务就报错。
NOT_SUPPORTED(不支持) 不支持在事务内运行,如果已经有事务了,就挂起当前存在的事务。
NEVER(从不使用) 不支持在事务内运行,如果已经有事务了,抛异常。
NESTED 开启一个子事务(MySQL不支持),需要支持还原点功能的数据库才行
> @Transactional默认的传播行为是REQUIRED,且一般情况下只用REQUIRED和REQUIRES_NEW,其他了解即可 @Transactional(propagation = Propagation.REQUIRED)
@Transactional(propagation = Propagation.REQUIRES_NEW)
2.为什么要控制事务的传播行为
一些人在需要使用事务控制的方法上,直接加上@Transactional注解,保证方法中一步出错,全部回滚,当然,一些情况下这样操作也没有问题。但一些业务场景下,不需要全部回滚,如保存一件商品需要:
保存该商品基本信息。 保存库存数量。 保存商品的参数值。 保存商品满减条件。
这样的场景下只需控制商品的基本信息和库存等核心数据保存成功后不管后面执行情况都不再回滚。
3.事务的传播行为使用例子
/**
*场景1
*A() 跟着回滚
*B() 使用新事务 不回滚
*自己操作数据库 回滚
*/
@Transactional
public void add(){
A(); //事务:REQUIRED
B(); //事务:REQUIRES_NEW
Mapper.xxx();
int i = 10/0;
}
/**
*场景2
*C方法抛异常:A,C共用add方法的事务,所以回滚,B使用新事务执行成功,D和自己操作数据库无法执行。
*场景3
*D方法抛异常:外事务感知到异常。A,C回滚,自己操作数据库不到,D自己回滚,B成功。
*场景4
*自己操作数据库出现异常:同理,A和C回滚,B和D成功。
*场景5
*若add()上没有@Transactional呢?效果还是一样,因为A传播行为是REQUIRED ,没有事务传播给它则自己创建一个事务,后面C则和A共享。B,D仍是自己新建事务。但是若自己操作数据库抛异常则。所有以上执行过方法不会回滚
*
*/
@Transactional
public void add(){
A(); //REQUIRED
B(); //REQUIRES_NEW
C(); //REQUIRED
D(); //REQUIRES_NEW
Mapper.xxx(); //自己操作数据
}
/**
*场景6:
*C用try-catch执行;C出了异常回滚,由于异常被捕获,add()的事务没有感知异常。A,B,D都执行成功,C自己回滚。
*/
@Transactional
public void add(){
A(); //REQUIRED
B(); //REQUIRES_NEW
try {
C(); //REQUIRED
}catch (Exception e){
e.printStackTrace();
}
D(); //REQUIRES_NEW
}
同一个类下方法的事务问题
/**
* 调用同一个类下方法的事务问题
*/
@Service
public class BrandService{
@Transactional()
public void Save(Brand brand, Long cid) {
//新增brand
this.brandMapper.insert(brand);
//新增中间表
this.brandMapper.insertCategoryAndBrand(cid, brand.getId());
doOther();
int i = 10 / 0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doOther() {
this.Mapper.insert("something");
}
}
/**
*按下面的分析,在 int i = 10/0 抛异常后,结果应该只有doOther方法里的数据库操作能成功,其他回滚,但是结
*果却是全部回滚。
*原因就是直接调用同类方法,相当于方法内的代码复制过来,最后doOther()上的注解不起作用,导致全部回滚。
*解决方法:因为这是spring,事务操作是基于AOP实现的,方法要开启事务需要代理,而BrandService注入容器就是
*本身的代理对象,所以只需要用这个对象调用doOther即可。
*修改如下:
/
@Service
public class BrandService{
@Autowired
private BrandService brandService;//spring已经解决了循环注入问题
@Transactional()
public void Save(Brand brand, Long cid) {
//新增brand
this.brandMapper.insert(brand);
//新增中间表
this.brandMapper.insertCategoryAndBrand(cid, brand.getId());
brandService.doOther();
int i = 10 / 0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doOther() {
this.Mapper.insert("something");
}
}
// 最后,只有dother()中数据库操作执行成功。
4.@Transactional注解失效的场景
- 1.把注解标注在非public修饰的方法上
TransactionalRepositoryProxyPostProcessor类中的computeTransactionAttribute方法判断了是public修饰的方法返回null
TransactionalRepositoryProxyPostProcessor.computeTransactionAttribute(){
if (this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
}
- 2.注解属性propagation(传播行为)配置错误
- 3.rollbackFor属性设置错误
- 4.在同一个类中方法调用,导致事务失败
- 5.自己主动去try,catch代表【没有异常】,导致事务失效
- 6.数据库引擎本身就不支持事务(例如MyISAM)。当然也不会生效
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
/**
* 事务失效的场景
*/
@Slf4j
@Service
public class TransactionalLose {
@Autowired
private TransactionalLose transactionalLose;
/**
* user_reg_info dao接口
*/
private final UserRegInfoRepository userRegInfoRepository;
public TransactionalLose(UserRegInfoRepository userRegInfoRepository) {
this.userRegInfoRepository = userRegInfoRepository;
}
@Transactional()
public void wrongRollbackForNo() throws Exception {
UserRegInfo userRegInfo = new UserRegInfo();
userRegInfo.setUserName("wrongRollbackFor");
userRegInfoRepository.save(userRegInfo);
//...
throw new IOException("throw IOException for check Rollback");
}
@Transactional(rollbackFor = Exception.class)
public void wrongRollbackFor() throws Exception {
UserRegInfo userRegInfo = new UserRegInfo();
userRegInfo.setUserName("wrongRollbackFor");
userRegInfoRepository.save(userRegInfo);
//...
throw new IOException("throw IOException for check Rollback");
}
/**
* <h2>同一个类中的方法调用</h2>
*/
@Transactional
public void wrongInnerCall() throws Exception {
this.wrongRollbackFor();
//...
}
/**
* <h2>同一个类中的方法调用</h2>
*/
@Transactional
public void wrongInnerCallUseAutowired() throws Exception {
transactionalLose.wrongRollbackFor();
//...
}
@Transactional(rollbackFor = Exception.class)
public void wrongTryCatch() {
try {
UserRegInfo userRegInfo = new UserRegInfo();
userRegInfo.setUserName("wrongRollbackFor");
userRegInfoRepository.save(userRegInfo);
// ....
// 由于某种原因抛出了异常
throw new IOException("throw io exception for check rollback");
} catch (Exception ex) {
log.error("has some error: [{}]", ex.getMessage(), ex);
}
}
}
5.总结
- 1.传播行为过程中,只要Requires_new被执行过就一定成功,不管后面出不出问题。异常机制还是一样的,出现异常代码以后不执行。Required只要感觉到异常就一定回滚。和外层事务(例子中的add方法上的事务)是什么传播行为无关。
- 2.业务逻辑中,需要考虑哪些东西是一定要回滚的、哪些即使出错了不必要回滚的。
- 3.在同类方法调用中一定要使用自己的代理对象(即自己注入IOC中的对象)调用
原文章链接:https://blog.csdn.net/adminBfl/article/details/128268337