@TransaTranctional之前一直以为只要在想要加注解的方法上加@Transactional注解就可以开启事务了,而对于其原理是不清楚的,但是其实其中的坑有点多,除了网上各大博主总结的几种情况(详情请看@Transactional失效的6种情况),自己在测试的也遇到其中一种情况(这是因为这是基于Spring的AOP代理实现的,后面搞懂了。。。),下面分析一下这种情况。
一个很简单的demo,接口代码省略。。
@Service
public class TestServiceImpl implements TestService {
@Autowired
public TestMapper testMapper;
@Autowired
public TestBService testBService;
@Transactional
@Override
public void a() {
try {
Test test = new Test();
test.setId(1);
test.setName("a");
testMapper.insert(test);
//直接调用b方法
this.b();
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional
@Override
public void a1() {
try {
Test test = new Test();
test.setId(2);
test.setName("a1");
testMapper.insert(test);
//新建一个类调用b方法 此b方法与下面的b方法一模一样
testBService.b();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void b() {
Test test = new Test();
test.setId(3);
test.setName("b");
testMapper.insert(test);
//模拟抛出一个RunTimeException
int i = 1 / 0;
}
}
@RestController
public class TestController {
@Autowired
TestService testService;
@RequestMapping("/a")
public String a(){
testService.a();
return "a";
}
@RequestMapping("/a1")
public String a1(){
testService.a1();
return "a1";
}
}
当访问 /a接口时,因为a方法默认事务传播行为为Propagation.REQUIRED,b方法事务为 REQUIRES_NEW
按照之前错误的理解方式的预期结果,b方法会开启一个新的事务,因为抛出异常,所以回滚了,id为3的用户插入不进去,但是a方法是单独的另外一个事务,并且通过trycatch捕获了b的异常(被trycatch捕获了异常,相当于没有发生异常,所以事务正常提交,开发中要注意),是可以插入进去的,但是经过事实证明,都插入进去了,为什么b方法也会插入进去呢?经过一步步debug,以及查阅相关资料。@Transactional是通过SpirngAop实现的
spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。
也就是说 我们this.b()方法实际没有使用代理类对象,我们通过debug也可以看到这一点,this这里不是指的代理类,所以事务没有生效。
如何解决呢?可以像a1()方法新建一个类,在调用b方法的时候会重新生成一个代理类,此时b方法@Transactional才会生效。b方法回滚,所以只有id为2的用户插入进去
@ Transactional在哪里开启事务的呢?经过一步步debug 发现是在
invokeWithinTransaction()->createTransactionIfNecessary()->getTransaction()->startTransaction()->doBegin() 在doBegin方法中可以看到熟悉的jdbc操作事务的步骤。。。。
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = this.obtainDataSource().getConnection();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
this.prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = this.determineTimeout(definition);
if (timeout != -1) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());
}
} catch (Throwable var7) {
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, this.obtainDataSource());
txObject.setConnectionHolder((ConnectionHolder)null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", var7);
}
}