Spring事务-代理模式导致事务不回滚问题分析
说明
首先我们都了解事务为什么回滚,回滚的原因是什么。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚。
问题描述
我们定义两个类,一个类中有两个事务方法,如图:
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.
测试结束-----------------
数据库: 插入两条数据
注: 这种情况符合我们异常抛出回滚,异常被捕获,不回滚的逻辑判断。但是别急,我们看下面这种情况。我们将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();
}
}
我们再来看下输出和数据库的情况:
输出:
数据库: 没有任何数据插入
所以,这是为什么呢?调用类自己内部的事务方法的时候,被调用的事务方法抛出异常,异常被捕获了,不会回滚;调用其他类中的事务方法,被调用的事务方法抛出异常,异常也被捕获了,我们看到异常打印出了catch the exception,但是事务回滚了,这是什么原因呢?
分析
我们从日志着手,首先,同样异常都被捕获住了,但是调用其他类中的事务方法的时候多抛出了一个异常,UnexpectedRollbackException,并且提示事务被回滚,因为rollback-only已经被标记成需要回滚了,那我们就根据异常一步一步分析吧。
我们已经知道回滚标记已经被置成true,异常是testService中抛出,我们看看为什么回滚标记,什么时候被置为true的。首先,事务的实现是基于代理模式实现,当条用testService中的test方法的时候触发了代理模式,而直接调用this.test(),因为this是被代理对象本身,所以并不会触发代理模式,这就是这两者区别的真正原因。