说明

  首先我们都了解事务为什么回滚,回滚的原因是什么。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚。

问题描述

我们定义两个类,一个类中有两个事务方法,如图:

package com.helu.samui.service;

import com.helu.samui.dao.UserInfoDao;
import com.helu.samui.entity.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "userInfoService")
public class UserInfoService {

    @Autowired
    private UserInfoDao userInfoDao;
    @Autowired
    private TestService testService;

    @Transactional
    public void save() {
        userInfoDao.insert(getUserInfo("a1"));
        try {

            //内部类调用不会触发代理模式
            this.test();
            //testService.test();
        } catch (Exception e) {
            System.out.println("aaaaaaaaaaaaaaa");
            e.printStackTrace();
        }
        userInfoDao.insert(getUserInfo("a2"));
    }

    @Transactional
    public void test() {
        throw new RuntimeException();
    }

    private UserInfo getUserInfo(String loginName) {
        UserInfo userInfo = new UserInfo();
        userInfo.setLoginName(loginName);
        return userInfo;
    }
}

如图: save()方法上加了@Transactional注解,表示开启了一个事务,在其中进行了两次插入操作,中间调用this.test()这个同一个类中方法,我们可以发现在代码执行到save()的时候,异常被捕获,事务未被回滚。
输出:

开始测试-----------------
catch the exception.
测试结束-----------------

数据库: 插入两条数据

springboot service 报错没有回滚 spring事物不回滚_回滚


注: 这种情况符合我们异常抛出回滚,异常被捕获,不回滚的逻辑判断。但是别急,我们看下面这种情况。我们将UserInfoService 中的save()方法中两次插入中间的调用改成testService.test(),去调用testService的test()方法。

package com.helu.samui.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "testService")
public class TestService {

    @Transactional
    public void test() {
        throw new RuntimeException();
    }
}

我们再来看下输出和数据库的情况:

输出:

springboot service 报错没有回滚 spring事物不回滚_代理模式_02


数据库: 没有任何数据插入

  所以,这是为什么呢?调用类自己内部的事务方法的时候,被调用的事务方法抛出异常,异常被捕获了,不会回滚;调用其他类中的事务方法,被调用的事务方法抛出异常,异常也被捕获了,我们看到异常打印出了catch the exception,但是事务回滚了,这是什么原因呢?

分析

  我们从日志着手,首先,同样异常都被捕获住了,但是调用其他类中的事务方法的时候多抛出了一个异常,UnexpectedRollbackException,并且提示事务被回滚,因为rollback-only已经被标记成需要回滚了,那我们就根据异常一步一步分析吧。
  我们已经知道回滚标记已经被置成true,异常是testService中抛出,我们看看为什么回滚标记,什么时候被置为true的。首先,事务的实现是基于代理模式实现,当调用testService中的test方法的时候触发了代理模式,而直接调用this.test(),因为this是被真实对象本身,所以并不会触发代理模式,这就是这两者区别的真正原因。