Bootstrap

SpringBoot事务机制学习

1、同一事务下,删除更新新增操作后,查到的数据是否是操作的最新数据

     答:是的,这个是于mysql事务的隔离性有关,由于事务的隔离性,本事的操作都是最新的数据,其他事务操作的都是本事务提交前的数据(mysql默认是可重复读,不会出现脏读、幻读、可重复读)

###查询mysql的事务隔离级别
select @@tx_isolation;

######一个事务中(假设表A)########
###开启事务
start transaction;
###查询当前数据
select * from A;
###保存一个保存点aa
savepoint aa;
####删除id为1的数据
delete from A where id=1;
####本事务中查询id=1的数据
select * from A  where id=1;>>查出无数据
####其他事务中查询id=1的数据
select * from A  where id=1;>>查出有数据
####回滚到aa (判断执行)
rollback to aa;

####提交事务
COMMIT;
####如果执行回滚后再次查询id=1的数据
select * from A  where id=1;>>查出有数据
####如果没有执行回滚后再次查询id=1的数据
select * from A  where id=1;>>查出无数据

2、父方法带@Transactional,子方法不带。子方法发生异常是否会全部回滚

      答:会进行回滚。只要事务开启了,子方法只要没有重新开启新事务,子方法还会在当前事务中,子方法发生异常后也会进行回滚。(如jpa中dao中的删除方法不带@Transactional,但是父类带了@Transactional是不会报错的)

       源码分析:

        关键在于processCommit方法org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit

//如果是一个新事务则提交事务	
else if (status.isNewTransaction()) {
	if (status.isDebug()) {
		    logger.debug("Initiating transaction commit");
		}
		unexpectedRollback = status.isGlobalRollbackOnly();
		doCommit(status);
}
//如果发生异常则进行回滚
catch (UnexpectedRollbackException ex) {
	// can only be caused by doCommit
	triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
	throw ex;
}
catch (TransactionException ex) {
    // can only be caused by doCommit
	if (isRollbackOnCommitFailure()) {
		doRollbackOnCommitException(status, ex);
	}
else {
    triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
	throw ex;
}
catch (RuntimeException | Error ex) {
	if (!beforeCompletionInvoked) {
	    triggerBeforeCompletion(status);
	}
doRollbackOnCommitException(status, ex);
	throw ex;
}

//最终提交,不管有没有发生异常或者有没有新事务都会执行这两个方法
// Trigger afterCommit callbacks, with an exception thrown there
// propagated to callers but the transaction still considered as committed.
try {
	triggerAfterCommit(status);
}
finally {
	triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}

关键点:最终提交的方法,进行了是否开启新旧事务的进行判断,如果是旧事务则不进行提交

可见没有开启新事务则不进行最终提交。在结束方法时或者是出现异常时会将状态制成新事务,进行最终的提交或者回滚。

总结:由于子方法没有开启新事务,所以出现异常会进行回滚,同理父方法出现异常后子方法也不会进行回滚。

 3、事务失效的场景一:方法不是使用public修饰

springBoot中在获取事务的属性的时候会进行一个判断,判断方法是否是public修饰的,如果不是使用public修饰的方法将返回一个null,导致事务不生效。

重点注意:修饰符导致不生效的是仅仅限于第一个使用@Transactional注解的方法上,子方法使用还是属于事务中

详见源码:org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource#getTransactionAttribute

中的computeTransactionAttribute方法

示例一:事务生效。(结合下面的4看) 

  @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteUnit(Integer id) {
        //删除下属单位
        unitDao.deleteByParentId(id);
        delete2(id);

    }

    private void delete2(Integer id) {
        //删除下属部门
        deptService.deleteByUnitId(id);
        int i=1/0;
        //删除单位
        unitDao.delete(id);
    }

示例二:事务不生效

//组件UnitServiceImpl中
@Override
public void deleteUnit2(Integer id) {
   Unit unit = unitDao.findOne(id);
   if (unit==null) {
       return;
    }
   //删除单位
    unitDao.delete(unit);
    unitServiceImpl2.deleteUnit(id);
}

//组件UnitServiceImpl2中
@Transactional(rollbackFor = Exception.class)
    protected void deleteUnit(  Integer id) {
        //删除下属单位
        unitDao.deleteByParentId(id);
        int i=1/0;
        //删除下属部门
        deptService.deleteByUnitId(id);

    }

 4、事务失效场景二:没经过组件的调用

   答: spring的事务是通过组件来调用的,没有经过组件调用的方法是不接受事务的管理

       1、 是指最顶级的方法没有加事务注解并且没经过组件的调用,子方法加了注解,则子方法的事务是不生效的(子方法和父方法在同一组件内)

下面实例事务不会回滚,下属部门照样被删

    @Override
    public void deleteUnit(Integer id) {
        //删除下属单位
        unitDao.deleteByParentId(id);
        delete2(id);

    }
    @Transactional(rollbackFor = Exception.class)
    public void delete2(Integer id) {
        //删除下属部门
        deptService.deleteByUnitId(id);
        int i=1/0;
        //删除单位
        unitDao.delete(id);
    }

源码分析:

deleteUnit中执行到delete2时,事务的状态变成新事务,即提交当前的删除。同样的进入delete2方法中后执行 deptService.deleteByUnitId(id)之后就立马提交了当前事务。

        2、是最顶级的方法加了事务注解,并且经过组件的调用,则子方法即使不带事务注解,子方法也会在事务内(同一)(子方法和父方法在同一组件内)

下面的代码会正常回滚

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteUnit(Integer id) {
        //删除下属单位
        unitDao.deleteByParentId(id);
        delete2(id);

    }
    public void delete2(Integer id) {
        //删除下属部门
        deptService.deleteByUnitId(id);
        int i=1/0;
        //删除单位
        unitDao.delete(id);
    }

5、事务失效场景三:trycatch

         1、自行trycatch异常后是不会被事务管理给捕捉到,导致回滚不生效。只有给事务管理抛出异常,让它知道你出现异常需要回滚,它才会进行回滚

  @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteUnit3(Integer id) {
        try{
            //删除下属单位
            unitDao.deleteByParentId(id);
            //删除下属部门
            deptService.deleteByUnitId(id);
            int i=1/0;
            //删除单位
            unitDao.deleteById(id);
        }catch (Exception e) {
            e.printStackTrace();
        }

    }

     2、没有指定具体异常 rollbackFor = Exception.class trycatch后catch抛出

       如下:

 @Override
    @Transactional
    public void deleteUnit3(Integer id) throws Exception {
        try{
            //删除下属单位
            unitDao.deleteByParentId(id);
            //删除下属部门
            deptService.deleteByUnitId(id);
            int i=1/0;
            //删除单位
            unitDao.deleteById(id);
        }catch (Exception e) {
            e.printStackTrace();
            throw new Exception("更新错误");
        }

    }

  如果不给rollbackFor指定默认值则默认回滚的异常是RuntimeException和Error,如果不是这两个类型或者子类是不会进行回滚

源码:

	/**
	 * Defines zero (0) or more exception {@link Class classes}, which must be
	 * subclasses of {@link Throwable}, indicating which exception types must cause
	 * a transaction rollback.
	 * <p>By default, a transaction will be rolling back on {@link RuntimeException}
	 * and {@link Error} but not on checked exceptions (business exceptions). See
	 * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
	 * for a detailed explanation.
	 * <p>This is the preferred way to construct a rollback rule (in contrast to
	 * {@link #rollbackForClassName}), matching the exception class and its subclasses.
	 * <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}.
	 * @see #rollbackForClassName
	 * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
	 */
	Class<? extends Throwable>[] rollbackFor() default {};

重点:所以一定加入异常注解时都要求强制指定回滚异常:    @Transactional(rollbackFor = Exception.class)

6、事务失效场景四:主动不支持事务

1、如:主动关闭事务:@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)

   如果它是被其它事务调用,其它在事务中的方法会回滚,它自己不会回滚

2、如:开启新事务

     此场景其实事务没有失效,只是开启了多个事务,异常后都可以照常回滚。但是如果是新事务的方法包含事务生效的子方法一旦执行完了就事务提交。

      新事务查询之前事务删除的数据,由于隔离性还是可以查到的。

7、如何解决 4事务失效场景二:没经过组件的调用的问题呢

1、方法一:把加事务的方法移到另一个组件中,再进行调用

2、方法二:组件自注入调用

@Service
public class UnitServiceImpl implements UnitService {
    @Resource
    private UnitDao unitDao;
    @Resource
    private DeptService deptService;
    @Resource
    private UnitService unitService;

    @Override
    public void deleteUnit3(Integer id) {
        unitService.delete3(id);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete3(Integer id){
        Unit unit = unitDao.findOne(id);
        if (unit==null) {
            return;
        }
        //删除单位
        unitDao.delete(unit);
        int i=1/0;
        //删除下属单位
        unitDao.deleteByParentId(id);
        //删除下属部门
        deptService.deleteByUnitId(id);
    }

3、方法三:使用Aop代理

 UnitService unitService = (UnitService) AopContext.currentProxy();
  unitService.delete3(id);

@Service
public class UnitServiceImpl implements UnitService {
    @Resource
    private UnitDao unitDao;
    @Resource
    private DeptService deptService;
    @Resource
    private UnitServiceImpl2 unitServiceImpl2;
//    @Resource
//    private UnitService unitService;

    @Override
    public void deleteUnit3(Integer id) {
        UnitService unitService = (UnitService) AopContext.currentProxy();
        unitService.delete3(id);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete3(Integer id){
        Unit unit = unitDao.findOne(id);
        if (unit==null) {
            return;
        }
        //删除单位
        unitDao.delete(unit);
        int i=1/0;
        //删除下属单位
        unitDao.deleteByParentId(id);
        //删除下属部门
        deptService.deleteByUnitId(id);
    }
}

;