【Mybatis】【SpringBoot】源码分析在有无 @Transitional 下的事务提交过程
1 前言
之前我们从数据库连接的角度看过 MyBatis 和 Spring 的交互,以及在动态数据源下的交互,那么我们这节看什么呢?看一下在有无 @Transitional 下的事务提交的差异。
想一个问题,事务提交或者回滚,最后的落点是什么?是不是就是那条数据库连接的提交或者回滚,就跟我们早之前的 JDBC 那样。其实是的,我们接下来就看看。
2 源码分析
2.1 无 @Transitional 下的事务处理
这个需要大家有一定的前置知识哈,因为我之前细讲了 Mapper 接口的代理生成过程,所以大家一点儿都不了解的话,需要先看看之前的哈,再过来看这里。
我们 Mapper 接口注入的是 MapperFactoryBean 生成的代理对象,并且是 JDK 的代理方式,那么当执行某个接口的方法时,是不是就被 InvocationHandler 的处理器拦截了,那么我们就从 MapperProxy 看起:
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -4724728412955527868L; private static final int ALLOWED_MODES = 15; private static final Constructor<Lookup> lookupConstructor; private static final Method privateLookupInMethod; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperProxy.MapperMethodInvoker> methodCache; // sqlSession? 是谁? public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperProxy.MapperMethodInvoker> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } // 方法代理 proxy 代理对象 method 要执行的方法 args 方法的参数 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 如果时 Object 的方法直接执行, 否则用 cachedInvoker 包装一下 return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession); } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } }
如上,增强的逻辑大致能懂,那么有一个问题,这个 SqlSession 是谁?知道这个很关键,我们看看它的来源,我简单画了个图哈:
可以看到 MapperProxy 内部的 SqlSession 就是我们的 SqlSessionTemplate,我们还得看下它的构造方法,这很重要:
public class SqlSessionTemplate implements SqlSession, DisposableBean { private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; private final SqlSession sqlSessionProxy; private final PersistenceExceptionTranslator exceptionTranslator; // 调用重载方法 public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType()); } // 调用重载 public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) { this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true)); } public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); Assert.notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; // 创建了一个 SqlSession 的代理对象 this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor()); } }
我们看到它内部创建了一个 SqlSession 的代理对象,并且所有的实际执行都交给内部的这个代理对象进行执行了:
...
public <T> T selectOne(String statement) { return this.sqlSessionProxy.selectOne(statement); } public <T> T selectOne(String statement, Object parameter) { return this.sqlSessionProxy.selectOne(statement, parameter); } public <K, V> Map<K, V> selectMap(String statement, String mapKey) { return this.sqlSessionProxy.selectMap(statement, mapKey); }
...
知道这个我们返回来看 Mapper 代理的执行:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try {
// Object 方法直接执行,其他的封装cachedInvoker然后执行 return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession); } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } }
我们看下 cachedInvoker 方法:
private MapperProxy.MapperMethodInvoker cachedInvoker(Method method) throws Throwable { try { return (MapperProxy.MapperMethodInvoker)MapUtil.computeIfAbsent(this.methodCache, method, (m) -> { // 接口的 default 方法 略过 if (m.isDefault()) { try { return privateLookupInMethod == null ? new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava8(method)) : new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava9(method)); } catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException var4) { throw new RuntimeException(var4); } } else { return new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration())); } }); } catch (RuntimeException var4) { Throwable cause = var4.getCause(); throw (Throwable)(cause == null ? var4 : cause); } }
可以看到最后会执行到:MapperProxy.PlainMethodInvoker,我们继续看:
private static class PlainMethodInvoker implements MapperProxy.MapperMethodInvoker { private final MapperMethod mapperMethod; public PlainMethodInvoker(MapperMethod mapperMethod) { this.mapperMethod = mapperMethod; } // invoke 执行 public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return this.mapperMethod.execute(sqlSession, args); } }
我们看看执行:
public Object execute(SqlSession sqlSession, Object[] args) { Object result; Object param; switch(this.command.getType()) { case INSERT: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); break; case UPDATE: param = this.method.convertArgsToSqlCommandParam(args);
// 调用 SqlSession 的 result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); break; case DELETE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); break; case SELECT: if (this.method.returnsVoid() && this.method.hasResultHandler()) { this.executeWithResultHandler(sqlSession, args); result = null; } else if (this.method.returnsMany()) { result = this.executeForMany(sqlSession, args); } else if (this.method.returnsMap()) { result = this.executeForMap(sqlSession, args); } else if (this.method.returnsCursor()) { result = this.executeForCursor(sqlSession, args); } else { param = this.method.convertArgsToSqlCommandParam(args);
// 拿这里的查询来说:是不是调用的 SqlSession 是不是就是调用的sqlSessionTemplate 的 selectOne result = sqlSession.selectOne(this.command.getName(), param); if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + this.command.getName()); } if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) { throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ")."); } else { return result; } }
可以看到增删改查都走的 SqlSession 的方法,SqlSession 就是我们刚才看的 SqlSessionTemplate,拿 selectOne 来说,继而是不是进入了:
public <T> T selectOne(String statement, Object parameter) { return this.sqlSessionProxy.selectOne(statement, parameter); }
是不是就是调用的代理对象的 selectOne,那我们得看看代理对象的增强逻辑:
private class SqlSessionInterceptor implements InvocationHandler { private SqlSessionInterceptor() { } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 获取一个 SqlSession 对象 默认是不自动提交的 SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); Object unwrapped; try { // 执行sql Object result = method.invoke(sqlSession, args); // 如果当前没有事务,那么就提交 if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { sqlSession.commit(true); } unwrapped = result; } catch (Throwable var11) { unwrapped = ExceptionUtil.unwrapThrowable(var11); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped); if (translated != null) { unwrapped = translated; } } throw (Throwable)unwrapped; } finally { // 关闭 sqlSeesion if (sqlSession != null) { SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } return unwrapped; } }
我们可以看到显示从 SqlSessionUtils 工具类获取一个 SqlSession 对象,然后执行语句,然后判断当前是否有事务注解,没有的话进行提交。这样无 @Transtional 的事务就提交了。
而 SqlSession 的事务提交,我们再继续看进去:
public void commit(boolean force) { try { // 内部执行器的提交 this.executor.commit(this.isCommitOrRollbackRequired(force)); this.dirty = false; } catch (Exception var6) { throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + var6, var6); } finally { ErrorContext.instance().reset(); } }
而内部执行器的提交:
public void commit(boolean required) throws SQLException { if (this.closed) { throw new ExecutorException("Cannot commit, transaction is already closed"); } else { this.clearLocalCache(); this.flushStatements(); if (required) { // 事务对象的提交 this.transaction.commit(); } } }
事务对象的提交:
public void commit() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { LOGGER.debug(() -> { return "Committing JDBC Connection [" + this.connection + "]"; }); // 连接的提交 this.connection.commit(); } }
看到最后,提交就是作用在数据库连接上的,是不是就跟我们老早 JDBC 的方式一样了。
我画个图再理一下,类太多了,我就挑了些关键的类和方法哈,SqlSession 内部的 Executor 详细的执行过程我这里就省略了:
我们再小结下:
(1)SqlSessionInterceptor 内部的增强逻辑,会先通过 SqlSessionUtils 获取一个 SqlSession
(2)SqlSessionUtils 首先会在 TransactionSynchronizationManager 根据 SqlSessionFactory 对象获取,有的话直接返回,没有的话会通过 SqlSessionFactory 创建,默认创建出的不会自动提交事务。创建完会往 TransactionSynchronizationManager 中缓存 SqlSession,存放的前提是当前存在事务
(3)SqlSessionInterceptor 在 SqlSession 执行完,有个判断当前是否在事务中,不在的话,则提交事务。
2.2 有 @Transitional 下的事务处理
有事务的情况下,其实就跟连接贴的比较近了,可以看我之前这篇串 spring 事务下和 mybatis 数据库连接的串联,开启事务后,连接会提前暴露,然后关闭自动提交,存放到事务上下文中,然后 mybatis 执行的时候,会从事务上下文中获取到那条连接进行执行哈,然后 spring 决定事务的提交或者回滚,我这里就不细看了哈。
3 小结
好啦,本节就看到这里哈。