(四)MyBatis整合SpringBoot源码解析——SQL执行流程

目录

概述

内容

1.整体流程

2.元数据获取

2-1.功能描述

2-2.主要流程代码

3.会话创建

3-1.功能描述

3-2.主要流程代码

4.执行器执行

4-1.功能描述

4-2.主要流程代码

5.会话关闭


概述

        经过前两章对MyBatis中一些基础内容介绍之后,本章主要讲解下MyBatis中SQL执行的核心流程。

 链接跳转

(一)MyBatis整合SpringBoot源码解析——整体大纲

(二)MyBatis整合SpringBoot源码解析——前置内容

(三)MyBatis整合SpringBoot源码解析——配置初始化

(四)MyBatis整合SpringBoot源码解析——SQL执行流程

注:代码部分仅列出主要代码,会省略一些不影响核心流程的分支以及异常处理,部分为伪代码,尽量保证简洁。


内容

1.整体流程

本章节中,将MyBatis中SQL执行流程拆解为4个子流程进行讲解,主要为元数据获取、会话创建、执行器执行、会话关闭。

2.元数据获取

2-1.功能描述

MyBatis核心流程的方法入口是在MapperProxy#Invoke,MapperProxy会对接口方法映射的元数据(MapperMethod)进行管理,在invoke方法中其实就是获取方法对应的MapperMethod,并委托给它执行。

2-2.主要流程代码

MapperProxy#Invoke

public class MapperProxy<T> implements InvocationHandler, Serializable {
    // 与Spring集成后,实际实现类是SqlSessionTemplate
    private final SqlSession sqlSession;
    // 接口信息
    private final Class<T> mapperInterface;
    // 方法对应的MapperMethod
    private final Map<Method,MapperMethodInvoker> methodCache;
    // 方法入口
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 从缓存中获取方法对应的MapperMethod,并且委托给它执行
        cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    }

    private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        return methodCache.computeIfAbsent(method, m -> {
            // 省略部分分支
            // 正常情况下使用PlainMethodInvoker进行包装MapperMethod,除非该方法是一个default方法。
            // PlainMethodInvoker内部实际还是直接调用MapperMethod.invoke方法
            return PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        });
    }
}

MapperMethod#execute,此处为伪代码

public class MapperMethod {
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        // 参数转换
        Object param = method.convertArgsToSqlCommandParam(args);
        switch (command.getType()) {
            case INSERT/UPDATE/DELETE: {
                // 根据INSERT/UPDATE/DELETE类型,选择执行sqlSession.insert/update/delete方法
                break;
            }
            case SELECT: {
                // 根据返回类型判定最终执行sqlSession.selectMap/selectList/selectCursor
                break;
            }
        }
        return result;
    }
}

3.会话创建

3-1.功能描述

获取到元数据之后,会根据SQL的类型去选择Sqlsession接口中的insert/update/delete/selectList方法执行,此处的Sqlsession接口的实现类是SqlSessionTemplate,该类只是做了会话和事务的管理,真正的执行逻辑还是委托给Sqlsession的另一个实现类(DefaultSqlSession)执行。首先创建会话(DefaultSqlSession)和事务(Transaction的实现类SpringManagedTransaction),然后将方法委托给DefaultSqlSession执行,执行完成之后再提交事务并且关闭会话。

3-2.主要流程代码

SqlSessionTemplate#invoke

public class SqlSessionTemplate implements SqlSession, DisposableBean {

    private class SqlSessionInterceptor implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 创建会话,详情见下面的getSqlSession
            SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
                    SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
            try {
                // 委托给另一个实现类DefaultSqlSession执行
                Object result = method.invoke(sqlSession, args);
                // 如果当前会话没有被Spring的事务管理(TransactionSynchronizationManager),则执行提交操作
                // 即如果开启了Spring的事务的话,MyBatis在此处不会提交事务,而是交给Spring的事务管理器去处理
                // 比如使用了@Transaction注解,那么此处就不会提交事务,而是委托给Spring的事务管理器处理。
                if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                    sqlSession.commit(true);
                }
                return result;
            }  finally {
                if (sqlSession != null) {
                    // 同理,如果当前会话被Spring的事务管理,则不会关闭会话
                    // 因为后续可能还会使用到,统一交由Spring的事务管理器去处理即可
                    // 反之关闭
                    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                }
            }
        }
    }

    public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
                                           PersistenceExceptionTranslator exceptionTranslator) {
        // 从线程上下文获取会话
        SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
        // 如果是在Spring事务管理下执行的话,此处即可以直接获取到会话并且直接返回
        // 也就是如果开启了事务情况下,事务内的多个语句执行都是用的同一个会话
        SqlSession session = sessionHolder(executorType, holder);
        if (session != null) {
            return session;
        }
        // 非Spring事务的情况下,每次执行SQL都会开启一个新的会话
        session = sessionFactory.openSession(executorType);
        return session;
    }

}

4.执行器执行

4-1.功能描述

DefaultSqlSession从元数据中获取方法对应的SQL语句,从而委托给Executor执行,Executor分多个实现,使用了包装器模式,BaseExecutor是基础的实现类,如果开启了二级缓存的情况下,Executor会被CacheExecutor装饰,执行SQL之前先从缓存中获取结果,二级缓存是同一个namespace下的所有语句(一般情况下对应每个xxMapper.xml文件),所有会话共享

4-2.主要流程代码

DefaultSqlSession#selectList、DefaultSqlSession#update

public class DefaultSqlSession implements SqlSession {

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        // 根据接口+方法名从全局配置类中获取对应的SQL
        MappedStatement ms = configuration.getMappedStatement(statement);
        // 委托给执行器执行,如果开启二级缓存
        // 那么此处的执行器会被CachingExecutor包装,调用链为CachingExecutor -> BaseExecutor,
        // 在CachingExecutor的query方法中,会从二级缓存(NameSpace级别)中获取数据,如果不存在再查询数据库
        // 而BaseExecutor的方法中,会从一级缓存(SqlSession级别)获取数据,如果不存在再查询数据库
        // 也就是如果一二级缓存都使用的情况下,二级缓存的优先级大于一级缓存。
        // 而最终执行的query方法中就是获取连接,并执行语句。
        return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    }

    @Override
    public int update(String statement, Object parameter) {
        // 根据接口+方法名从全局配置类中获取对应的SQL
        MappedStatement ms = configuration.getMappedStatement(statement);
        // 不论执行的是update/insert/delete,最终底层调用的都是update方法
        // 同查询一样,此处也会委托给执行器执行,如果开启二级缓存
        // 那么此处的执行器会被CachingExecutor包装,调用链为CachingExecutor -> BaseExecutor,
        // 在CachingExecutor的update方法中,会先去清除二级缓存,在执行update操作。
        // 而BaseExecutor的方法中,会先去清除一级缓存,在执行update操作。
        // 而最终执行的update方法中就是获取连接,并执行语句。
        return executor.update(ms, wrapCollection(parameter));
    }
}

5.会话关闭

会话关闭的代码参考3-2。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值