美文网首页
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