今天在做项目的时候遇到一个问题,和之前的理论知识不符,之前在学习spring transaction事务的时候,如果自己对方法抛出的异常进行了捕获了,事务是不会回滚的,但是今天我测试后发现并不是这样,那个大佬能帮我解决下,万分感谢!
下面是我的业务场景,一个类A加了事务管理,其中一个方法methedA调用了另个一个类B的方法methedB,类B也加了事务管理,类B的methedB抛出了一个异常,methedA对异常进行了捕获,但是数据还是没有插入到数据库,具体代码如下:
类A:
@Service
@Transactional(rollbackFor = Exception.class)
public class InvoiceLoggingService implements IInvoiceLoggingService {    private static final Logger logger = LoggerFactory.getLogger(InvoiceLoggingService.class);    @Autowired
    private InvoiceBatchInputService batchService;
   @Autowired
    private InvoiceInputRecordDao dao;   /**
     * 发票录入
     *
     * @param record
     * @return
     */
    @Override
    public InvoiceBatchLoggingResponse invoiceLogging(InvoiceInputRecord record) {
InvoiceBatchLoggingResponse response = new InvoiceBatchLoggingResponse();
        //根据发票代码和发票号码判断该发票是否重复录入,如果重复录入就标记为重复
        try {
            String invoiceCode = record.getInvoiceCode();
            String invoiceNumber = record.getInvoiceNumber();
            InvoiceInputRecord checkRecord = this.dao.findByInvoiceCodeAndInvoiceNum(invoiceCode, invoiceNumber);            record.setCreateTime(new Date());
            record.setUpdateTime(new Date());
            if (checkRecord != null) {
                logger.debug("发票代码:{},发票号码:{}已经存在,本次为重复录入", invoiceCode, invoiceNumber);
                record.setDuplicateState((byte) 1);
            }            //插入记录
            record.setInvoiceInputRecordId(this.idGenHelper.getLongId());
            this.dao.insertRecord(record);            //调用后续接口
            com.kingxunlian.caf.modules.logging.dto.InvoiceLoggingResponse res = this.batchService.invoiceLogging(record);
            CommonUtils.beanCopy(res, response);        } catch (Exception e) {
            e.printStackTrace();
            logger.error("发票查验异常,信息为:{}", e);        }
        return response;
    }
}类B:
@Service
@Transactional(rollbackFor = Exception.class)
public class InvoiceBatchInputService implements IInvoiceBatchInputService {    private static final Logger logger = LoggerFactory.getLogger(InvoiceBatchInputService.class);    /**
     * 发票批次录入dao
     */
    @Resource
    private InvoiceInputRecordDao invoiceInputRecordDao;    /**
     * 发票批次录入
     *
     * @param invoiceInputRecord
     * @return
     */
    @Override
    public InvoiceLoggingResponse invoiceLogging(InvoiceInputRecord invoiceInputRecord) {        logger.debug("进入发票批次录入");
        InvoiceLoggingResponse loggingResponse = new InvoiceLoggingResponse();
    
            CommonUtils.beanCopy(invoiceInputRecord, loggingResponse);
            String userId = invoiceInputRecord.getUserId();
            // 根据userId获取用户名
            UserInfoResponse userResponse = this.userFeignClient.getUserById(userId).getBody();
            if (userResponse == null) {
                logger.error("根据userId获取用户信息失败!");
                throw new Exception("当前用户不存在!");
            }            logger.debug("根据用户id:{}获取用户名:{}", userId, userResponse.getNickName());
            invoiceInputRecord.setUserName(userResponse.getNickName());            this.invoiceInputRecordDao.updateByInvoiceInputRecordId(invoiceInputRecord.getInvoiceInputRecordId(), invoiceInputRecord);            // 1.真假验证
            this.validateReal(invoiceInputRecord);            loggingResponse.setSuccess(true);
            loggingResponse.setDescription("发票录入操作成功");
               return loggingResponse;
    }当B类方法中userResponse 为null的时候,类A中的方法insertRecord()并没有保存数据到数据库,不知道这个是为什么?但是我将类B的@Transactional(rollbackFor = Exception.class)去掉后就可以保存到数据库中

解决方案 »

  1.   

    可能和事务的合并有关系,试试传播行为改成 require_new   
      

  2.   

    同一个事务,在里层标有 @Transactional 的方法如果抛异常了,异常状态会传播到外层的 @Transactional,try catch 没用。
      

  3.   

    这个不是很正常?
    事务是aop实现的,aop是基于动态代理,代理即代理模式,是基于类的,你B类方法执行完就会标记事务回滚,在A方法捕获异常毫无意义
    你去掉@Transactional(rollbackFor = Exception.class)那B类方法没有代理事务咯,那就肯定是A来管事务咯
    如果你在A捕获异常,然后再插入一条记录,会抛出异常的
      

  4.   

    @Transactional传播行为默认是Propagation.REQUIRED,相当于我在A类中调用B类的方法是是属于同一个事务,为什么A类中捕获了还是会回滚呢?
      

  5.   

    当B类方法中userResponse 为null的时候,类A中的方法insertRecord()并没有保存数据到数据库,不知道这个是为什么?但是我将类B的@Transactional(rollbackFor = Exception.class)去掉后就可以保存到数据库中 
    你这个结果是正确的,刚开始两个方法都加了事务,那么会根据@Transactional传播行为默认是Propagation.REQUIRED 处于同一个事务中,所以你插入的数据被回滚到,因此没有保存到数据库中;第二次你将类B的注解去掉,那么B不是一个事务方法,抛出的异常不会被A监听到,这其实和事务的传播属性没有关系,应该去看看Spring的事务管理器是怎么玩的,它是分管理器和拦截器两部分,你要理解为什么加了注解,就能保证事务。
      

  6.   

    @Transactional传播行为默认是Propagation.REQUIRED,相当于我在A类中调用B类的方法是是属于同一个事务,为什么A类中捕获了还是会回滚呢?
    我上面不是已经解释了么?B类方法抛出异常,只要出这个方法就会标记事务回滚(只是标记回滚,还没有回滚)
      

  7.   

    @Transactional传播行为默认是Propagation.REQUIRED,相当于我在A类中调用B类的方法是是属于同一个事务,为什么A类中捕获了还是会回滚呢?
    我上面不是已经解释了么?B类方法抛出异常,只要出这个方法就会标记事务回滚(只是标记回滚,还没有回滚)那为什么有的地方说对异常进行捕获后就不会回滚,我这里也进行了捕获,为什么事务还是进行了回滚,麻烦您帮我讲解下,实在是没有理解
      

  8.   


    我感觉我上面白说了,我已经给你解释了,为啥B会回滚的问题
    这个东西的确不是很容易理解,你需要了解spring如何实现事务控制
    如果这个代码还看不懂,那就真没法说了package cn.diege.facade;public class TransactionTest { static ThreadLocal<TransactionManager> t = new ThreadLocal<>(); public static void main(String[] args) {
    //这个简单的模拟aop控制事务的过程,实际上更复杂
    A a = new ProxyA();
    a.a();
    } public static class A {
    B b = new ProxyB(); public void a() {
    try{
    b.b();
    }catch(Exception e){
    e.printStackTrace();
    }
    }
    } public static class B {
    public void b() {
    throw new RuntimeException("抛出异常了");
    }
    } public static class ProxyA extends A {
    A a = new A(); public void a() {
    getManager().begin();
    try {
    a.a();
    getManager().commit();
    } catch (Exception e) {
    getManager().rollback();
    }
    }
    } public static class ProxyB extends B {
    B b = new B(); public void b() {
    getManager().begin();
    try {
    b.b();
    } catch (Exception e) {
    //实际代码并非这么简单,他要判断当前是否事务最外层,这里简单的标记回滚
    getManager().setRollbackOnly();
    throw e;
    }
    }
    } public static class TransactionManager { private boolean flag = false; public void begin() { }; public void commit() {
    if(!flag){
    System.out.println("事务提交了");
    }else{
    //实际此时会抛出异常,你可以尝试在你的a方法里加一个增删改操作,估计也会抛异常
    System.out.println("事务已经标记回滚");
    }
    }; public void rollback() {
    System.out.println("事务回滚了");
    }; public void setRollbackOnly() {
    this.flag = true;
    }
    } public static TransactionManager getManager() {
    TransactionManager manager = t.get();
    if (manager == null) {
    t.set(new TransactionManager());
    }
    return t.get();
    }
    }
      

  9.   

    上面说的很多了,不懂就先背下来。
    然后想知道为什么,就是补补。
    spring事务怎么实现的,怎么管理的,就知道了。
    简而言之为什么会这样:
    a方法调用b方法(不同类),会在b方法的时候启动代理,然后在执行b的方法。代理发现已经有事务了就把b的事务标记为当前同一个事务。b抛出异常了就把事务标记为异常,然后抛出异常。a的try catch这个时候捕获了异常,也没有用应为事务已经是异常了。
    如果去掉注解,就不会有这个代理,try catch就是你想要的效果了。当然你可以b上面每次new一个事务也是可以的。,
      

  10.   


    教你一步步搭建ssm框架,第三步数据库事务验证及ssm常见事务不起作用排除 - 2018
      

  11.   

    我是这样理解的
    当B类上的事务注解没去掉的时候,A事务方法调用B事务方法,当B抛出异常后,虽然catch住了,但是在A中会出现事务重复提交异常,所以事务都会回滚的。
      

  12.   

    B类已经标记事务回滚,A类标记事务提交,导致产生了冲突;但是如果取消掉B类事务,B类自然将异常抛出,A类将异常捕获,就不出现事务回滚这种情况