简单的研究下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为例)
- spring事务控制入口 代理了所有被事务包围的执行方法
org.springframework.transaction.interceptor.TransactionInterceptor#invoke
- 此类有多个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 {
}
-
在类TransactionSynchronizationManager中,有个getResource()方法,通过此方法,SqlSessionUtils可以拿到SqlSessionHolder,这个对象持有sqlSession对象,并在整个事务过程被反复使用,但目前看不到它在哪里被放置到TransactionSynchronizationManager.resources中
org.springframework.transaction.support.TransactionSynchronizationManager#getResource
-
在类TransactionSynchronizationUtils中,在真正commit或者rollback前,有个调用invokeBeforeCommit/invokeAfterCommit和triggerBeforeCompletion/triggerAfterCompletion,这些方法会调用外部对象的方法,这些方法是开放给外部框架的接口,比如mybatis可以在这里把之前放入的SqlSessionHolder删除。
org.springframework.transaction.support.TransactionSynchronizationUtils