Spring事务管理的实现方式

以mybatis为例

Posted by me2in on November 15, 2021

简单的研究下Spring与mybatis事务的整合原理

1.mybatis自身的事务实现

​ 我们知道如果要jdbc事务的实现方式是在获取connect时设置autoCommit为false,并在执行完业务代码之后,手动调用commit方法。注意到这里的connect与事务是绑定的,想实现事务,你的业务代码就必须使用同一的connect,否则事务是无效的。

​ 先来一段mybatis的事务代码

public void trans(){
	//这里getSqlSession时可传入一个boolean值,设置autoCommit属性
	SqlSession sqlSession = SqlSessionFactory.getSqlSession();
    //实际返回的是MapperProxy的对象,这个对象持有sqlSession对象
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    //在执行mapper方法时,这是才会真正的getConnect,
    userMapper.updateUser(1,"lisan");
    //实际上调用的jdbc的connect.commit()方法
	sqlSession.commit();
}

​ 可以看到上面并没有出现getConnect和commit方法,那么mybatis怎么保证connect唯一呢?

​ 问题就在于这个getMapper方法上,实际上getMapper返回的是一个MapperProxy类型的对象,这个对象持有sqlSession,你的每次查询实际上调用的都是MapperProxy.invoke方法,在间接的调用了sqlSession内部方法。那么既然使用的都是同一个sqlSession,想在内部控制使用同一个connect就是很简单的事了。

 //org.apache.ibatis.binding.MapperProxy#invoke
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //如果是object原生方法,直接invoke
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        //这里才是真正调用的方法,可以看到传入了sqlSession对象
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

 //org.apache.ibatis.binding.MapperProxy.PlainMethodInvoker#invoke
 public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) 	throws Throwable {
      return mapperMethod.execute(sqlSession, args);
 }

 //org.apache.ibatis.binding.MapperMethod#execute
 public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
          case INSERT: {
            Object param = method.convertArgsToSqlCommandParam(args);
            //可以看到是使用了sqlSession方法进行了查询
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
          }
         ......
    }
    ......
  }

​ 那么sqlSession如何获取connect的?

​ 下图说明了mybatis中的对象嵌套关系,我们拿到的UserMapper,是最外层的MapperProxy,通过层层的嵌套,sqlSession对象内部持有一个transaction对象,mybatis正是通过这个对象的getConnect方法获取到连接并进行事务管理的,默认的实现是org.apache.ibatis.transaction.managed.ManagedTransaction,在mybatis-spring中我们还会发现另一个实现类org.mybatis.spring.transaction.SpringManagedTransaction,SpringManagedTransaction的最主要的功能是在getConnect()时调用了org.springframework.jdbc.datasource.DataSourceUtils#getConnection,通过这个方法,可以把当前的connect置于当前的线程变量中,这个spring就可以管理到当前的connect,并进行后续的管理

2.spring的事务管理实现(以mybatis为例)

  1. spring事务控制入口 代理了所有被事务包围的执行方法
org.springframework.transaction.interceptor.TransactionInterceptor#invoke
  1. 此类有多个threadlocal变量,并且类型为final static,推测应该与spring的事务实现有很大关联,因为实现事务管理的核心就是要让多个数据库操作共享同一个connect,而使用threadlocal可以使得代码得以解耦
org.springframework.transaction.interceptor.TransactionAspectSupport
/**   在TransactionAspectSupport存在这个子类,封装了事务的一些信息
	 * Opaque object used to hold transaction information. Subclasses
	 * must pass it back to methods on this class, but not see its internals.
	 */
	protected static final class TransactionInfo {
	}
  1. 在类TransactionSynchronizationManager中,有个getResource()方法,通过此方法,SqlSessionUtils可以拿到SqlSessionHolder,这个对象持有sqlSession对象,并在整个事务过程被反复使用,但目前看不到它在哪里被放置到TransactionSynchronizationManager.resources中

    org.springframework.transaction.support.TransactionSynchronizationManager#getResource
    
  2. 在类TransactionSynchronizationUtils中,在真正commit或者rollback前,有个调用invokeBeforeCommit/invokeAfterCommit和triggerBeforeCompletion/triggerAfterCompletion,这些方法会调用外部对象的方法,这些方法是开放给外部框架的接口,比如mybatis可以在这里把之前放入的SqlSessionHolder删除。

    org.springframework.transaction.support.TransactionSynchronizationUtils