spring 事务源码(三)如何保证被@Transactional标记方法中的所有sql都在一个事务内

661 阅读1分钟

这是我参与8月更文挑战的第20天,活动详情查看:8月更文挑战

核心问题

spring 如何保证被事务注解标记方法中的所有sql都在一个事务内

测试代码

还是之前的代码,但是这次得改下,想办法让sql抛出异常

<select id="getNum" resultType="java.lang.Integer">
        select count(*)
        from ss
        where 1 = 1;
    </select>
 @Transactional
    public void test(){
        courseItemService.ss();

    }

public void ss(){
        baseMapper.getNum();
    }

运行项目可以看到如下异常堆栈

在这里插入图片描述

最开始调用myabtis的类开始网上查就能查到

源码

org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  // 获取sqlsession
  SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
      SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
  try {
    Object result = method.invoke(sqlSession, args);
    if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
      // 如果当前线程上下文没有事务,则自动提交
      sqlSession.commit(true);
    }
    return result;
  } catch (Throwable t) {
    Throwable unwrapped = unwrapThrowable(t);
    if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
      // 关闭链接
      closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      sqlSession = null;
      .....
    }
    throw unwrapped;
  } finally {
    if (sqlSession != null) {
      closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
    }
  }
}

org.mybatis.spring.SqlSessionUtils#getSqlSession(.....)

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

		// 获取当前线程中的sqlsession持有对象
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
		// 获取sqlsession
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }
		// 如果sqlsession不存在则创建
    session = sessionFactory.openSession(executorType);

   // 注册sqlsession 到当前线程上下文
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

org.mybatis.spring.SqlSessionUtils#registerSessionHolder

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
  SqlSessionHolder holder;
  // 如果当前线程上下文有正在运行的事务
  if (TransactionSynchronizationManager.isSynchronizationActive()) {
    Environment environment = sessionFactory.getConfiguration().getEnvironment();

    if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
      LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");

      holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
      // 将SqlSessionHolder 绑定到当前线程
      TransactionSynchronizationManager.bindResource(sessionFactory, holder);
      TransactionSynchronizationManager
          .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
      holder.setSynchronizedWithTransaction(true);
      holder.requested();
    } else {
      .........
    }
  } else {
    .....
  }

}

总结

原理其实很简单,就是在当前线程上下文中放置一个sqlsession对象,方法中任何一个sql执行先去判断是否存在,如果存在则用存在的,否则去创建一个新的