传播行为是方法之间调用事务采取的 策略问题,比如执行多个insert操作,传统的是出现异常,全部回滚,但是现在不想全部回滚,正常的数据继续执行insert操作。
public enum Propagation {
/**
*需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务 ,
*去否则新建一个事务运行子方法
*/
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
/**
* 无论当前事务是否存在,都会创建新事务运行方法,
* 这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
*/
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
/**
* 在当前方法调用子方法时,如果子方法发生异常,
* 只因滚子方法执行过的 SQL ,而不回滚当前方法的事务
*/
NESTED(TransactionDefinition.PROPAGATION_NESTED);
}
REQUIRED和REQUIRED_NEW的最主要的区别是,REQUIRED_NEW不管当前事务是否存在,都新创建一个事务,而REQUIRED当前事务存在,则沿用当前的事务。
REQUIRE_NEW和NEST的区别是NEST会沿用当前事务的隔离级别,而REQUIRE_NEW则相当于是一个新的事务空间。
2 传播失效的问题
场景:批量添加用户信息,在一个service中创建两个方法,一个是批量添加batchAddUser(),一个是单个添加addUser(),批量添加定义传播属性值为REQUIRED,即不存在则创建事务,而存在则沿用之前的事务,单个添加用户传播属性为REQUIRES_NEW,即永远创建新的事务,在批量添加用户中通过for循环调用单个添加客户方法。
打开日志:
#日志配置
logging.level.root=DEBUG
logging.level.org.springframework=DEBUG
logging.level.org.org.mybatis=DEBUG
核心方法如下:
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void batchAddUser() {
User user=null;
for(int i=0;i<10;i++){
user=new User();
user.setName("传播"+i);
user.setPassword("传播"+i);
addUser(user);
}
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addUser(User user){
userDao.insertSelective(user);
}
如图中,没有创建新的事务。
原因: 在自调用的过程中 , 是类自身的调用 ,而不是代理对象去调用, 那么就不会产生 AOP , 这样 Spring
就不能把你的代码织入到约定的流程中 , 于是就产生了现在看到的失败场景。
解决方案:从Spring IOC容器中获取代理对象,去启动AOP,
@Service
public class UserBatchServiceImpl implements UserBatchService,ApplicationContextAware {
@Autowired
private UserDao userDao;
private ApplicationContext applicationContext;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addUser(User user){
userDao.insertSelective(user);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void batchAddUser() {
User user=null;
for(int i=0;i<10;i++){
user=new User();
user.setName("传播"+i);
user.setPassword("传播"+i);
//addUser(user);======================================================
UserBatchService userBatchService = applicationContext.getBean(UserBatchService.class);
userBatchService.addUser(user);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
由日志可以看出,调用子方法是,挂起当前事务,创建新的事务,执行完后提交,然后唤醒之前的事务。
记录出现的一个异常
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:724)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:504)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656)
在父方法中调用子方法,子方法出现异常,在添加注解和不添加注解以及添加propagation为Propagation.REQUIRES_NEW的情况下是否出现如上异常吗,测试代码如下。
@Transactional
@Override
public void testException() {
try {
userService.innorExcep(1);
} catch (Exception e) {
logger.error("执行异常", e);
}
User user = new User();
user.setUserName("u1");
user.setPassword("p1");
userMapper.insertSelective(user);
}
@Override
//@Transactional(propagation = Propagation.REQUIRES_NEW)
//不会出现
@Transactional
//会出现
//不加注解
//不会出现
//@Transactional(propagation = Propagation.NESTED)
//不会出现
public void innorExcep(int u) {
User user = new User();
user.setUserName("u2");
user.setPassword("p2");
userMapper.insertSelective(user);
if (u == 1) {
throw new IllegalArgumentException("参数错误");
}
}
添加@Transactional注解后出现异常,发生异常后,事务中设置了回滚标志位,虽然都被外层给破获了,但是事务在提交时发现了标志位,会回滚。
@Transactional(propagation = Propagation.NESTED)和@Transactional(propagation = Propagation.REQUIRES_NEW)是因为是一个单独的事务,不会干涉外层的事务。
innorExcep方法不加注解,那么就不会产生事务,那么就不会设置回滚标志位。
如果testException方法不加注解,不会开启事务,testException中就不会有提交事务的检查 ,try catch后执行sql直接提交。
如下代码,设置了回滚标志位,finally中的更新代码不会生效。
@Override
@Transactional
public void generateQualityTasks() {
User user=new User();
user.setId(2);
try {
userService.error();
user.setPassword("没有问题");
}catch (Exception e){
logger.error("出现异常", e);
user.setPassword("出现异常");
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}finally {
//--------------修改数据失效--------------
userMapper.updateByPrimaryKeySelective(user);
}
}
@Transactional(rollbackFor = Exception.class)
@Override
public void error(){
System.out.println(1/0);
}