【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  小结

好啦,本节就看到这里哈。

posted @ 2024-03-05 11:11  酷酷-  阅读(31)  评论(0编辑  收藏  举报