美文网首页
spring事务传播遇坑杂记

spring事务传播遇坑杂记

作者: Allen丶Joe | 来源:发表于2018-06-22 02:05 被阅读0次

一.业务场景

在调用其他服务接口进行数据同步时,同步数据成功则入库,同步数据失败时,则记录失败信息到日志表中,并回滚同步操作。

/**
 * 
* @ClassName: ZncjSyncInfoService 
* @Description: 同步业务类
* @author allen
* @date 2018年6月8日 下午3:20:45
*
 */
@Service
@Transactional
public class ZncjSyncInfoService {
    
    /**
     * 同步数据
     */
    public void syncDetail(ZncjSyncParam zncjSyncParam){
        ZncjSyncInfo syncEntity=new ZncjSyncInfo();
        try {
            BeanUtils.copyProperties(zncjSyncParam,syncEntity);
            syncEntity.setSyncTime(DateUtil.now());
            //持久化到库
            if(this.saveOrUpdate(syncEntity)==0)
                this.throwRuntimeEx("智能排序同步数据入库失败"); 
        } catch (Exception e) {
            // TODO: handle exception
            //同步异常时处理 ,并继续抛出异常,保存当前事务能够正常回滚
            this.handlerFailLog(syncEntity, e.getMessage());
            this.throwRuntimeEx(e);
        }
    }

       /**
       * 同步失败时,保存记录失败日志
       */
    public int handlerFailLog(ZncjSyncInfo syncEntity,String failMessage){
        ZncjSyncFailLog syncFailEntity=new ZncjSyncFailLog();
        BeanUtils.copyProperties(syncEntity, syncFailEntity);
        syncFailEntity.setFailContent(failMessage);
        syncFailEntity.setCreateTime(syncEntity.getSyncTime());
        int result=this.daoUtil.zncjSyncFailLogMapper.insertSelective(syncFailEntity);
        if(result==0)
            this.throwRuntimeEx("添加同步失败日志信息的操作出错啦!");
        return result;
     }

}

上面代码没能达到预期的结果(同步数据入库失败时,回滚数据,并且日志信息入库成功)
失败原因有如下:

  1. 同一个业务类调用业务方法时不会发生事务,即handlerFailLog()方法没有产生事务,spring事务默认的Aop模式使得spring事务拦截器只对外部方法调用有效。注意,日志表之所以会回滚,是因为syncDetail()存在事务,内部方法调用时,handlerFailLog()自动加入到事务中,并不是handlerFailLog()自身的事务导致回滚的。

  2. spring事务的传播行为,默认值为Propagation.REQUIRED,即如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。因此即使将handlerFailLog()方法移动到外部类中进行调用,满足产生事务的条件,handlerFailLog()方法产生的事务也会自动加入syncDetail()方法的事务中,导致日志数据回滚。

二.解决方案

主要的解决途径是将handlerFailLog()方法的事务传播级别设置为Propagation.REQUIRES_NEW(重新创建一个新的事务,如果当前存在事务,暂停当前的事务),让handlerFailLog()方法产生的事务不加入到当前事务中,在独立的事务中运行。另外,解决方法内部调用不产生事务的方式有两种:

  • 第一种,将handlerFailLog()方法移动到外部方法中,然后再进行调用,代码实现如下。
/**
 * 
* @ClassName: ZncjSyncInfoService 
* @Description: 同步业务类
* @author allen
* @date 2018年6月8日 下午3:20:45
*
 */
@Service
@Transactional
public class ZncjSyncInfoService {
    @Resource
    private ZncjSyncFailLogService zncjSyncFailLogService;
    
    /**
     * 同步数据
     */
    public void syncDetail(ZncjSyncParam zncjSyncParam){
        ZncjSyncInfo syncEntity=new ZncjSyncInfo();
        try {
            BeanUtils.copyProperties(zncjSyncParam,syncEntity);
            syncEntity.setSyncTime(DateUtil.now());
            //持久化到库
            if(this.saveOrUpdate(syncEntity)==0)
                this.throwRuntimeEx("智能排序同步数据入库失败"); 
        } catch (Exception e) {
            // TODO: handle exception
            //同步异常时处理 ,并继续抛出异常,保存当前事务能够正常回滚
            this.zncjSyncFailLogService.handlerFailLog(syncEntity, e.getMessage());
            this.throwRuntimeEx(e);
        }
    }
}

/**
 * 
* @ClassName: ZncjSyncFailLogService
* @Description: 同步失败日志业务类
* @author allen
* @date 2018年6月8日 下午3:20:45
*
 */
@Service
public class ZncjSyncFailLogService{
    
    /**
     * 同步失败时,保存记录失败日志
     */
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public int handlerFailLog(ZncjSyncInfo syncEntity,String failMessage){
        ZncjSyncFailLog syncFailEntity=new ZncjSyncFailLog();
        BeanUtils.copyProperties(syncEntity, syncFailEntity);
        syncFailEntity.setFailContent(failMessage);
        syncFailEntity.setCreateTime(syncEntity.getSyncTime());
        int result=this.daoUtil.zncjSyncFailLogMapper.insertSelective(syncFailEntity);
        if(result==0)
            this.throwRuntimeEx("添加同步失败日志信息的操作出错啦!");
        return result;
    }

}
  • 第二种,将业务类ZncjSyncInfoService通过spring的注解注入到自身中,实现方式相对于第一种比较简单,代码实现如下。
/**
 * 
* @ClassName: ZncjSyncInfoService 
* @Description: 同步业务类
* @author allen
* @date 2018年6月8日 下午3:20:45
*
 */
@Service
@Transactional
public class ZncjSyncInfoService {

       @Resource
       private ZncjSyncInfoService zncjSyncInfoService ;
    
    /**
     * 同步数据
     */
    public void syncDetail(ZncjSyncParam zncjSyncParam){
        ZncjSyncInfo syncEntity=new ZncjSyncInfo();
        try {
            BeanUtils.copyProperties(zncjSyncParam,syncEntity);
            syncEntity.setSyncTime(DateUtil.now());
            //持久化到库
            if(this.saveOrUpdate(syncEntity)==0)
                this.throwRuntimeEx("智能排序同步数据入库失败"); 
        } catch (Exception e) {
            // TODO: handle exception
            //同步异常时处理 ,并继续抛出异常,保存当前事务能够正常回滚
            this.zncjSyncInfoService.handlerFailLog(syncEntity, e.getMessage());
            this.throwRuntimeEx(e);
        }
    }

       /**
       * 同步失败时,保存记录失败日志
       */
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public int handlerFailLog(ZncjSyncInfo syncEntity,String failMessage){
        ZncjSyncFailLog syncFailEntity=new ZncjSyncFailLog();
        BeanUtils.copyProperties(syncEntity, syncFailEntity);
        syncFailEntity.setFailContent(failMessage);
        syncFailEntity.setCreateTime(syncEntity.getSyncTime());
        int result=this.daoUtil.zncjSyncFailLogMapper.insertSelective(syncFailEntity);
        if(result==0)
            this.throwRuntimeEx("添加同步失败日志信息的操作出错啦!");
        return result;
     }

}

三.知识点梳理

  1. 默认配置下 Spring 只会回滚运行时、未检查异常(继承自 RuntimeException 的异常)或者 Error。
  2. @Transactional 注解只能应用到 public 方法才有效。
  3. 内部方法调用不能被spring事务拦截器拦截到,不能产生事务。
  4. @Transactional 注解的属性介绍:
value 和 transactionManager 属性

它们两个是一样的意思。当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。

propagation 属性

事务的传播行为,默认值为 Propagation.REQUIRED。
可选的值有:

  • Propagation.REQUIRED

    如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。

  • Propagation.SUPPORTS

    如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

  • Propagation.MANDATORY

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

  • Propagation.REQUIRES_NEW

    重新创建一个新的事务,如果当前存在事务,暂停当前的事务。

  • Propagation.NOT_SUPPORTED

    以非事务的方式运行,如果当前存在事务,暂停当前的事务。

  • Propagation.NEVER

    以非事务的方式运行,如果当前存在事务,则抛出异常。

  • Propagation.NESTED

    和 Propagation.REQUIRED 效果一样。

isolation 属性

事务的隔离级别,默认值为 Isolation.DEFAULT。
可选的值有:

  • Isolation.DEFAULT(使用底层数据库默认的隔离级别)
  • Isolation.READ_UNCOMMITTED
  • Isolation.READ_COMMITTED
  • Isolation.REPEATABLE_READ
  • Isolation.SERIALIZABLE
timeout 属性

事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

readOnly 属性

指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollbackFor 属性

用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

noRollbackFor 属性

抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

相关文章

网友评论

      本文标题:spring事务传播遇坑杂记

      本文链接:https://www.haomeiwen.com/subject/xfchyftx.html