侧边栏壁纸
博主头像
林雷博主等级

斜月沉沉藏海雾,碣石潇湘无限路

  • 累计撰写 132 篇文章
  • 累计创建 47 个标签
  • 累计收到 3 条评论

目 录CONTENT

文章目录

5、MyBatis执行流程解析

林雷
2022-04-25 / 2 评论 / 0 点赞 / 1,045 阅读 / 38,069 字

一 创建SqlSession

通过上文的分析,我们继续解析MyBatis的流程。当解析完mybatis-config.xml和相关的映射文件之后,最后创建的SqlSessionFactory是DefaultSqlSessionFactory,由示例程序可知,接下来就是打开一个SqlSession了:

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = factory.openSession();
BlogMapper blogMapper = session.getMapper(BlogMapper.class);

SqlSession是与数据库交互的一次会话,而SqlSessionFactory是SqlSession的创建工厂。如下图所示,是SqlSessionFactory的实现:
SqlSessionFactory-fa252f02

如下,是SqlSessionFactory的接口声明:

public interface SqlSessionFactory {
    /**
     * 打开一个SqlSession
     * @return SqlSession
     */
    SqlSession openSession();

    /**
     * 打开一个SqlSession
     * @param autoCommit 是否自动提交
     * @return SqlSession
     */
    SqlSession openSession(boolean autoCommit);

    /**
     * 打开一个SqlSession, 指定Connection
     * @param connection Connection
     * @return SqlSession
     */
    SqlSession openSession(Connection connection);

    /**
     * 打开一个SqlSession, 指定隔离级别
     * @param level 隔离级别
     * @return SqlSession
     */
    SqlSession openSession(TransactionIsolationLevel level);

    /**
     * 打开一个SqlSession
     * @param execType 执行类型
     * @return SqlSession
     */
    SqlSession openSession(ExecutorType execType);

    /**
     * 打开一个SqlSession
     * @param execType 执行类型
     * @param autoCommit 是否自动提交
     * @return SqlSession
     */
    SqlSession openSession(ExecutorType execType, boolean autoCommit);

    /**
     * 打开一个SqlSession
     * @param execType 执行类型
     * @param level 隔离级别
     * @return SqlSession
     */
    SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);

    /**
     * 打开一个SqlSession
     * @param execType 执行类型
     * @param connection Connection
     * @return SqlSession
     */
    SqlSession openSession(ExecutorType execType, Connection connection);

    /**
     * 获取Configuration配置
     * @return Configuration配置对象
     */
    Configuration getConfiguration();
}

我们知道这里是DefaultSqlSessionFactory,我么看其openSession() 的实现:

@Override
public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
/**
* 从DataSource中打开一个SqlSession
* @param execType 类型
* @param level 隔离级别
* @param autoCommit 是否自动提交
* @return SqlSession
*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;//事务
    try {
        final Environment environment = configuration.getEnvironment();//获取环境信息
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);//获取事务工厂
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);//创建一个事务
        final Executor executor = configuration.newExecutor(tx, execType);//创建执行器
        return new DefaultSqlSession(configuration, executor, autoCommit);//创建DefaultSqlSession
    } catch (Exception e) {//出现异常
        closeTransaction(tx); //停止事务
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

这里使用Configuration.defaultExecutorType,而我们在4.1、MyBatis标签解析 1.1.2 初始化Configuration对象 中已经知道,defaultExecutorType是ExecutorType.SIMPLE;然后调用openSessionFromDataSource() 获取SqlSession,并且这里autoCommit(自动提交)为false的,所以如果存在事务的话,这里是需要手动提交事务的。最后创建DefaultSqlSession返回。

1.1 创建事务

在上文中(3.3 MyBatis基础支持层-DataSource、Transaction 二 Transaction),我们知道MyBatis提供了事务接口,我们在mybatis-config.xml配置文件中指定了JDBC类型的事务。这里通过DefaultSqlSessionFactory.getTransactionFactoryFromEnvironment() 方法获取创建事务的工厂:

/**
* 获取TransactionFactory
* @param environment environment
* @return TransactionFactory
*/
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
        return new ManagedTransactionFactory();
    }
    return environment.getTransactionFactory();
}

主要是通过Environment对象获取事务工厂,而我们在4.1、MyBatis标签解析 1.2.8.2 解析transactionManager标签 中已经介绍了这里使用的其实是JdbcTransactionFactory,这里不做重复介绍。

然后通过JdbcTransactionFactory创建一个Transaction,这里创建的是JdbcTransaction

/**
* 创建一个JdbcTransaction事务
* @param ds 数据源
* @param level 事务隔离级别
* @param autoCommit 是否自动提交
* @return Transaction
*/
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
}

这里的level为空,autoCommit为false。

1.2 创建执行器Executor

Executor是MyBatis核心接口之一,其中定义了数据库操作的基本方法。在实际应用中经常涉及SqlSession接口的功能,都是基于Executor接口实现的。Executor接口声明如下:

public interface Executor {
    /** 空的ResultHandler */
    ResultHandler NO_RESULT_HANDLER = null;

    /**
     * 更新操作
     * @param ms MappedStatement
     * @param parameter 参数
     * @return 更新成功的数量
     * @throws SQLException 异常
     */
    int update(MappedStatement ms, Object parameter) throws SQLException;

    /**
     * 查询操作
     * @param ms MappedStatement
     * @param parameter 参数
     * @param rowBounds 绑定的参数
     * @param resultHandler 结果集处理器
     * @param cacheKey 缓存的Key
     * @param boundSql 绑定的SQL
     * @param <E> 类型
     * @return 集合
     * @throws SQLException 异常
     */
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

    /**
     * 查询操作
     * @param ms MappedStatement
     * @param parameter 参数
     * @param rowBounds 绑定的参数
     * @param resultHandler 结果集处理器
     * @param <E> 类型
     * @return 集合
     * @throws SQLException 异常
     */
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

    /**
     * 查询操作
     * @param ms MappedStatement
     * @param parameter 参数
     * @param rowBounds 绑定的参数
     * @param <E> 类型
     * @return Cursor
     * @throws SQLException 异常
     */
    <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

    /**
     * 刷新
     * @return BatchResult
     * @throws SQLException 异常
     */
    List<BatchResult> flushStatements() throws SQLException;

    /**
     * 提交
     * @param required 是否强制
     * @throws SQLException 异常
     */
    void commit(boolean required) throws SQLException;

    /**
     * 回滚
     * @param required 是否强制
     * @throws SQLException 异常
     */
    void rollback(boolean required) throws SQLException;

    /**
     * 创建缓存Key
     * @param ms MappedStatement
     * @param parameterObject 参数
     * @param rowBounds RowBounds
     * @param boundSql BoundSql
     * @return CacheKey
     */
    CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

    /**
     * 是否有缓存
     * @param ms MappedStatement
     * @param key 缓存key
     * @return true/false
     */
    boolean isCached(MappedStatement ms, CacheKey key);

    /**
     * 清除本地缓存
     */
    void clearLocalCache();

    void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

    /**
     * 获取事务对象
     * @return Transaction
     */
    Transaction getTransaction();

    /**
     * 关闭当前会话
     * @param forceRollback 是否回滚
     */
    void close(boolean forceRollback);

    /**
     * 当前会话是否关闭
     * @return true/false
     */
    boolean isClosed();

    void setExecutorWrapper(Executor executor);
}

如下图所示,是Executor的实现:
Executor-aaef62e8

这里主要使用了模板模式和装饰器模式,CachingExecutor扮演了装饰器的角色,为Executor添加了二级缓存的功能。我们先不看具体的实现,等真正执行使用时再做详细介绍。我们先不看MyBatis的创建Executor流程,我们先看Executor的相关实现。

1.2.1 BaseExecutor及一级缓存简介

BaseExecutor是实现了Executor接口的抽象类,它实现了Executor接口的大部分方法,其中就是使用了模板方法模式。BaseExecutor中主要提供了缓存管理和事务管理的基本功能,继承BaseExecutor的子类只要实现四个基本方法来完成数据库的相关操作即可,这四个方法分别是:doUpdate()方法、doQuery()方法、doQueryCursor()方法、doFlushStatement()方法,其余的功能在BaseExecutor中实现。BaseExecutor中各个字段含义如下:

/** 事务对象 */
protected Transaction transaction;

/** 封装的Executor对象 */
protected Executor wrapper;

/** 延迟加载队列 */
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;

/** 一级缓存, 用于缓存该Executor对象查询结果集映射得到的结果对象 */
protected PerpetualCache localCache;

/** 存储过程输出参数缓存 */
protected PerpetualCache localOutputParameterCache;

/** 配置对象 */
protected Configuration configuration;

/** 查询栈的层次 */
protected int queryStack;

/** 是否已经关闭 */
private boolean closed;

1.2.1.1 一级缓存

在常见的企业级系统中,数据库是比较珍贵的资源,很容易成为整个系统的瓶颈。在设计和维护系统时,会进行多方面的权衡,减少对数据库的直接访问。使用缓存是一种比较有效的优化手段,使用缓存可以减少应用系统与数据库的网络交互、减少数据库访问次数、减低数据库的负担等一系列开销,从而提高整个系统的性能。

MyBatis也提供了缓存的功能,其缓存设计为两层结构,分别为一级缓存和二级缓存,二级缓存在后面介绍。

一级缓存是会话级别的缓存,在MyBatis中每创建一个SqlSession对象,就表示开始一次数据库会话。在一次会话中,应用程序可能会在短时间内,例如一个事务内,反复执行完全相同的查询语句,如果不对数据库进行缓存,那么每次查询都会执行一次数据库查询操作,而多次完全相同的、时间间隔较短的查询语句得到的结果集有可能完全相同,这也造成了数据库资源的浪费。

MyBatis中的SqlSession是通过Executor对象完成数据库操作的,为了避免上述问题,在Executor对象中会建立一个简单的缓存,也就是一级缓存,它会将每次查询的结果对象缓存起来,在执行查询操作时,会先查询一级缓存,如果其中存在完全一样的查询语句,则直接从一级缓存中取出相应的结果对象并返回给用户。

一级缓存的生命周期与SqlSession相同,其实也就是与SqlSession中封装的Executor对象的生命周期相同。当调用Executor对象的close()方法时,该Executor对象对应的一级缓存就变得不可用。一级缓存中对象的存活时间受很多方面的影响,例如,在调用Executor.update()方法时,就会先清空一级缓存。

1.2.1.2 一级缓存管理

执行select语句查询数据库是最常用的功能,BaseExecutor.query() 方法实现该功能,代码如下:

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
                         CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    //如果已经关闭则直接抛出异常 Start
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    //如果已经关闭则直接抛出异常 End

    //清空缓存(第一次查询或者配置了刷新缓存) Start
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    //清空缓存(第一次查询或者配置了刷新缓存) End

    List<E> list;
    try {
        queryStack++;
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;//查询一级缓存
        if (list != null) {//缓存不为空
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {//缓存为空查询数据库
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        queryStack--;//当前查询完成, 查询层数减少
    }
    if (queryStack == 0) {
        //延迟加载 Start
        for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        //延迟加载 End
        deferredLoads.clear();

        //缓存级别是SQL语句级别也清除缓存 Start
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            clearLocalCache();
        }
        //缓存级别是SQL语句级别也清除缓存 End
    }
    return list;
}

我们先不看具体的实现,可以看到,首先是通过CacheKey从localCache(PerpetualCache对象)缓存中获取数据,如果存在就直接返回;否则才会去查询数据库。这个localCache其实就是一级缓存。其执行流程如下图所示:
clipboard-209c5193

而对于具体的细节,我们稍后在流程中再具体介绍。

1.2.2 SimpleExecutor

SimpleExecutor继承了BaseExecutor抽象类,它是最简单的Executor接口实现,一级缓存等固定不变的操作都封装到了BaseExecutor中,在SimpleExecutor中不必关系一级缓存等操作,只需要关注实现4个基本方法的实现即可。
如下是SimpleExecutor.doQuery() 方法的具体实现:

/**
* 执行查询
* @param ms MappedStatement
* @param parameter 参数
* @param rowBounds RowBound
* @param resultHandler 结果处理器
* @param boundSql BoundSql
* @param <E> 类型
* @return 集合
* @throws SQLException 异常
*/
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
                           BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();//获取Configuration对象
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        stmt = prepareStatement(handler, ms.getStatementLog());//准备Statement
        return handler.query(stmt, resultHandler);//执行查询并对结果集进行处理ResultSetHandler
    } finally {
        closeStatement(stmt);
    }
}

对于doQueryCursor()、update()这里就不做介绍了,可以自行研究。
而对于ReuseExecutor和BatchExecutor我这里也不做单独介绍了,可自行研究。

1.2.3 CachingExecutor及二级缓存简介

CachingExecutor是一个Executor接口的装饰器,它为Executor对象增加了二级缓存的相关功能。

1.2.3.1 二级缓存简介

MyBatis中提供的二级缓存是应用级别的缓存,它的生命周期与应用程序的生命周期相同,与二级缓存相关的配置有三个:
(1) 首先是在mybatis-config.xml配置文件中的cacheEnabled配置,它是二级缓存的总开关,只有当该配置设置为true时,后面的两项配置才会有效果,具体配置如下:

<!-- 全局配置信息 Start -->
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
<!-- 全局配置信息 End -->

(2) 需要在映射文件中配置cache节点或cache-ref节点
如果映射配置文件中配置了这两者中的任一一个节点,则表示开启了二级缓存功能。

(3) 最后一个配置项是select节点中的useCache属性,该属性表示查询操作产生的结果对象是否要保存到二级缓存中。useCache属性的默认值是true。
如下图所示,是二级缓存使用的示意图:
clipboard-3ef3f0e9

当应用程序通过SqlSession2执行定义在命名空间namespace2中的查询操作时,SqlSession2首先到namespace2对应的二级缓存中查找是否缓存了相应的结果对象。如果没有,则继续到SqlSession2对应的一级缓存中查找是否缓存了相应的结果对象,如果依然没有,则访问数据库获取结果集并映射结果对象返回。最后,该结果对象会记录到SqlSession对应的一级缓存一级namespace2对应的二级缓存中,等待后续使用。由于namespace3与namespace2共享同一个二级缓存对象,所以通过SqlSession3执行命名空间namespace3中的完全相同的查询操作时,可以直接从二级缓存中得到相应的结果对象。

对于各个Executor具体实现我们这里先不做过多解释,等后续进行详细介绍。

1.2.4 创建Executor

到这里,我们已经知道Executor是SqlSession底层与数据库交互的对象。我们回到DefaultSqlSessionFactory.openSessionFromDataSource()方法看创建Executor,具体通过Configuration.newExecutor() 方法创建:

/**
* 创建一个Executor
* @param transaction 事务对象
* @param executorType Executor类型
* @return Executor
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {//BatchExecutor
        executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {//ReuseExecutor
        executor = new ReuseExecutor(this, transaction);
    } else {//SimpleExecutor
        executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {//如果开启了缓存, 使用CachingExecutor
        executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);//通过调用pluginAll()方法创建Executor的代理对象
    return executor;
}

首先根据executorType创建指定的Executor,我们的示例使用的是ExecutorType.SIMPLE,所以这里创建了SimpleExecutor,如果开启了二级缓存的话(cacheEnabled为true)则将SimpleExecutor封装到CachingExecutor中。最后调用拦截器链,如果存在指定的拦截器的话,则获取Executor对象;否则是Executor原对象。

1.2.5 MyBatis插件之Executor拦截器

这里我们接触到了MyBatis的一个拦截器(插件),在上文解析MyBatis的配置文件中(4.1、MyBatis标签解析 1.2.4 解析plugins节点)我们解析了MyBatis的<plugins>标签,而<plugins>标签就是配置拦截器的标签。这里我们具体看一下MyBatis拦截器的使用。
如下,我们编写一个简单的拦截器:

@Intercepts(
        {@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,
                ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,
                ResultHandler.class})})
public class DemoPlugin implements Interceptor {
    private Properties properties;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("===================================== 拦截器分隔符 =====================================");
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            System.out.println("===================================== 拦截器分隔符 =====================================");
            System.out.println("目标类: " + target.getClass());
        }
        return Interceptor.super.plugin(target);
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

这里首先使用注解 @Intercepts 注解,里面使用 @Signature 表示方法的签名,其属性有:

  • type:指定拦截的地方。主要有Executor、ParameterHandler、ResultSetHandler、StatementHandler
  • method:指定目标类中的方法
    • Executor:update()、query()、flushStatements()、commit()、rollback()、getTransaction()、close()、isClosed()方法
    • ParameterHandler:getParameterObject()、setParameters()方法
    • ResultSetHandler:handleResultSets()、handleOutputParameters()方法
    • StatementHandler:prepare()、parameterize()、batch()、update()、query()方法
  • args:方法的参数的类型。由于在目标方法存在很多重载,所以这里需要指定目标方法的参数类型去定位具体的方法

编写好拦截器之后,需要在mybatis-config.xml中进行配置:

<plugins>
    <plugin interceptor="com.bianjf.mybatis.mybatis.plugins.DemoPlugin">
        <property name="key" value="value"/>
    </plugin>
</plugins>

此时,在做查询功能的时候,就会打印相关内容了。具体效果可自行测试,我这里不做演示了。
现在使用我们知道了,并且上文也解析过<plugins>标签了,这里我们先分析Executor拦截器的使用:

executor = (Executor) interceptorChain.pluginAll(executor);//通过调用pluginAll()方法创建Executor的代理对象

通过 4.1、MyBatis标签解析 1.2.4 解析plugins节点 分析我们知道,解析出来的<plugins>标签的内容是存放到Configuration.interceptorChain拦截器执行链中,所以这里调用InterceptorChain.pluginAll() 方法:

/**
* 获取目标对象的代理对象或原对象
* @param target 目标对象
* @return 代理对象或原对象
*/
public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {//遍历所有的拦截器
        target = interceptor.plugin(target);//调用其plugin()方法判断是否需要进行代理的
    }
    return target;//返回
}

我们示例中的plugin()方法是调用父类Interceptor.super.plugin()方法的,这个方法主要是获取代理对象的功能,示例中没有做逻辑,直接使用父类的Interceptor.plugin() 方法:

/** 决定是否触发intercept()方法 */
default Object plugin(Object target) {
    return Plugin.wrap(target, this);
}
/**
* 构建代理对象
* @param target 目标对象
* @param interceptor 拦截器
* @return 代理对象
*/
public static Object wrap(Object target, Interceptor interceptor) {
    //获取用户自定义Interceptor中的@Signature注解的信息, getSignatureMap()方法负责处理@Signature注解
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();//获取目标Class
    //获取目标类型实现的interface(JDK动态代理必须有实现类的interface)
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);


    //创建JDK动态代理 Start
    if (interfaces.length > 0) {
        return Proxy.newProxyInstance(
                type.getClassLoader(),
                interfaces,
                new Plugin(target, interceptor, signatureMap));
    }
    //创建JDK动态代理 End
    return target;//没有的话直接返回原对象
}

这里主要做了以下功能:

  • getSignatureMap获取方法的签名,这里主要解析@Interceptors和@Signature注解,获取目标类的相关方法。最终封装成Class -> Method集合的关系。等使用时,查询对应的方法是否在集合中,在集合中就会走到代理类中;否则直接走原方法
  • MyBatis使用的是JDK动态代理,所以这里需要获取目标target对应的interface,通过getAllInterfaces()方法获取
  • 最后进行判断,如果存在signatureMap数据,那么就创建动态代理,使用的代理对象为Plugin;否则直接返回原对象

1.2.5.1 解析方法签名

解析方法签名,返回Map<Class, Set<Method>>集合,代码Plugin.getSignaureMap() 代码如下:

/**
* 获取拦截器中@Signature注解的信息
* @param interceptor 拦截器
* @return keywei@Signature的type属性, value是拦截的方法集合
*/
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);//获取@Intercepts注解

    //如果实现了Interceptor接口, 但是没有@Intercepts注解, 则直接报错 Start
    if (interceptsAnnotation == null) {
        throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
    }
    //如果实现了Interceptor接口, 但是没有@Intercepts注解, 则直接报错 End
    Signature[] sigs = interceptsAnnotation.value();//获取@Signature注解
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();//拦截的方法集合
    for (Signature sig : sigs) {
        Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
        try {
            Method method = sig.type().getMethod(sig.method(), sig.args());//获取对应的Method方法
            methods.add(method);//添加到Set集合中
        } catch (NoSuchMethodException e) {
            throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
        }
    }
    return signatureMap;
}

getSignatureMap(Interceptor),首先获取Interceptor对应的注解@Intercepts,如果不存在则直接抛出异常;然后获取@Intercepts的value,即@Signature注解数组,遍历@Signature注解数组,获取其type对应的Class,然后获取该Class对应的method,如果method不存在的话,则抛出异常;最后将获取到的Method对象添加到methods的Set集合中。

这里返回的是一个Map集合,后续使用有两个作用:

  1. 判断Map集合是否有数据,如果有数据则创建代理对象;否则是原对象
  2. 判断是否存在对应的方法,如果存在对应的方法,则走到拦截器Interceptor.intercept()方法中;否则直接执行对应Method.invoke()

1.2.5.2 获取目标对象的interface

getAllInterfaces() 代码如下:

private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<>();
    while (type != null) {
        for (Class<?> c : type.getInterfaces()) {
            if (signatureMap.containsKey(c)) {
                interfaces.add(c);
            }
        }
        type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
}

1.2.5.3 Plugin

最后,会创建Plugin代理对象。所以我们知道,Plugin对象肯定是一个InvocationHandler对象,并且重写了invoke() 方法:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        Set<Method> methods = signatureMap.get(method.getDeclaringClass());//获取对应的方法

        //如果当前的方法需要被拦截, 则调用interceptorintercept()方法进行拦截处理 Start
        if (methods != null && methods.contains(method)) {
            return interceptor.intercept(new Invocation(target, method, args));
        }
        //如果当前的方法需要被拦截, 则调用interceptorintercept()方法进行拦截处理 End

        return method.invoke(target, args);//如果当前调用的方法不用被拦截, 则调用target对象的相应方法
    } catch (Exception e) {
        throw ExceptionUtil.unwrapThrowable(e);
    }
}

这里就是代理对象的执行方法逻辑。首先看对应的Class是否存在Set<Method>集合,如果不存在那么拦截的就不是该方法,所以直接执行method.invoke()方法;如果有Set<Method>集合,并且该集合中包含了method,那么执行我们编写的拦截器Interceptor.intercept()方法,传递的参数的是Invocation对象,该方法中只有一个方法,即执行目标方法:

/**
* 执行方法
* @return 执行的结果
* @throws InvocationTargetException 异常
* @throws IllegalAccessException 异常
*/
public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);//执行方法
}

到这里,MyBatis的拦截器逻辑其实就很清楚了:

  • 首先编写一个类,实现Interceptor接口,并重写其中的intercept()方法,该方法就是用户编写的拦截逻辑;同时需要配置注解@Intercepts和@Signature以完成拦截作用
  • 而通过Interceptor.plugin()方法我们可以决定是否执行Interceptor.intercept()方法,plugin()方法主要判断因素:
    • 解析@Intercepts和@Signature后,会有一个Map<Class, Set>集合;如果执行的目标方法不在解析出来的集合中,那么不创建代理对象,即原对象原方法。最终执行方法也是原方法的Method.invoke()
    • 如果目标方法在解析出来的Map集合中,那么就会创建代理对象Plugin,而Plugin.invoke()方法是执行Interceptor.intercept()方法,即我们上面编写的拦截器方法
  • 创建代理对象Plugin传递的是Invocation对象,通过Invocation对象最终执行到目标方法Method.invoke()中

程序走到这里也就创建好了Executor对象了,接下来就是封装到SqlSession对象中使用了。

1.3 SqlSession

通过上文我们知道,最终创建了一个SqlSession对象(DefaultSqlSession),这是用户可以操作的接口层对象。我们先看一下SqlSession接口的相关实现:
SqlSession-3c176ca5
这里只有两个实现。我们再看一下SqlSession的接口声明:

/**
* MyBatis框架主要的执行SQL的接口
* @author Clinton Begin
*/
public interface SqlSession extends Closeable {
    /**
     * 查询一个对象
     * @param statement SQL语句
     * @param <T> 对象类型
     * @return 类型
     */
    <T> T selectOne(String statement);

    /**
     * 查询一个对象
     * @param statement SQL语句
     * @param parameter 参数
     * @param <T> 对象类型
     * @return 类型
     */
    <T> T selectOne(String statement, Object parameter);

    /**
     * 查询一个集合
     * @param statement SQL语句
     * @param <E> 类型
     * @return List集合
     */
    <E> List<E> selectList(String statement);

    /**
     * 查询一个集合
     * @param statement SQL语句
     * @param parameter 参数
     * @param <E> 类型
     * @return List集合
     */
    <E> List<E> selectList(String statement, Object parameter);

    /**
     * 查询一个集合
     * @param statement SQL语句
     * @param parameter 参数
     * @param rowBounds 在statement的SQL语句后面执行rowBounds参数
     * @param <E> 类型
     * @return List集合
     */
    <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

    /**
     * 查询一个Map集合。例如查询一个Map<Integer,Author>可以这样: selectMap("selectAuthors", "id")
     * @param statement SQL语句
     * @param mapKey key
     * @param <K> key
     * @param <V> value
     * @return Map
     */
    <K, V> Map<K, V> selectMap(String statement, String mapKey);

    /**
     * 查询一个Map集合。例如查询一个Map<Integer,Author>可以这样: selectMap("selectAuthors", "id")
     * @param statement SQL语句
     * @param parameter 参数
     * @param mapKey key
     * @param <K> key
     * @param <V> value
     * @return Map
     */
    <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

    /**
     * 查询一个Map集合。例如查询一个Map<Integer,Author>可以这样: selectMap("selectAuthors", "id")
     * @param statement SQL语句
     * @param parameter 参数
     * @param mapKey key
     * @param rowBounds 绑定的参数
     * @param <K> key
     * @param <V> value
     * @return Map
     */
    <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

    /**
     * 查询游标
     * @param statement SQL语句
     * @param <T> 类型
     * @return Cursor
     */
    <T> Cursor<T> selectCursor(String statement);

    /**
     * 查询游标
     * @param statement SQL语句
     * @param parameter 参数
     * @param <T> 类型
     * @return Cursor
     */
    <T> Cursor<T> selectCursor(String statement, Object parameter);

    /**
     * 查询游标
     * @param statement SQL语句
     * @param parameter 参数
     * @param rowBounds 绑定的SQL
     * @param <T> 类型
     * @return Cursor
     */
    <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);

    /**
     * 查询。将结果封装到ResultHandler中
     * @param statement SQL语句
     * @param parameter 参数
     * @param handler ResultHandler
     */
    void select(String statement, Object parameter, ResultHandler handler);

    /**
     * 查询。将结果封装到ResultHandler中
     * @param statement SQL语句
     * @param handler ResultHandler
     */
    void select(String statement, ResultHandler handler);

    /**
     * 查询。将结果封装到ResultHandler中
     * @param statement SQL语句
     * @param parameter 参数
     * @param rowBounds 绑定的SQL
     * @param handler ResultHandler
     */
    void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);

    /**
     * insert操作
     * @param statement SQL语句
     * @return 添加的数量
     */
    int insert(String statement);

    /**
     * insert操作
     * @param statement SQL语句
     * @param parameter 参数
     * @return 添加的数量
     */
    int insert(String statement, Object parameter);

    /**
     * update操作
     * @param statement SQL语句
     * @return 更新的数量
     */
    int update(String statement);

    /**
     * update操作
     * @param statement SQL语句
     * @param parameter 参数
     * @return 更新的数量
     */
    int update(String statement, Object parameter);

    /**
     * delete操作
     * @param statement SQL语句
     * @return 删除的数量
     */
    int delete(String statement);

    /**
     * delete操作
     * @param statement SQL语句
     * @param parameter 参数
     * @return 删除的数量
     */
    int delete(String statement, Object parameter);

    /**
     * 事务提交
     */
    void commit();

    /**
     * 事务提交
     * @param force 是否强制
     */
    void commit(boolean force);

    /**
     * 事务回滚
     */
    void rollback();

    /**
     * 事务回滚
     * @param force 是否强制
     */
    void rollback(boolean force);

    /**
     * 刷新
     * @return BatchResult
     */
    List<BatchResult> flushStatements();

    /**
     * Closes the session.
     */
    @Override
    void close();

    /**
     * 清空缓存
     */
    void clearCache();

    /**
     * 获取Configuration
     * @return Configuration对象
     */
    Configuration getConfiguration();

    /**
     * 获取Mapper
     * @param type Mapper
     * @param <T> 类型
     * @return Mapper对象
     */
    <T> T getMapper(Class<T> type);

    /**
     * 获取数据库连接
     * @return Connection
     */
    Connection getConnection();
}

这里主要就是对数据库进行增删改查的操作,接下来我们以查询操作为例去分析。这里使用的SqlSession对象是DefaultSqlSession

二 获取Mapper

在我们使用MyBatis过程中,通常使用方式是通过SqlSession获取我们的Mapper对象,然后执行Mapper对象的相应方法,类似代码如下:

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = factory.openSession();
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
Blog blog = blogMapper.selectDetails(1);

当我们创建出SqlSession对象(DefaultSqlSession)后,就会调用getMapper() 方法获取Mapper对象:

@Override
public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}
/**
* 获取Mapper代理对象
* @param type 类型
* @param sqlSession SqlSession
* @param <T> 类型
* @return Mapper代理对象
*/
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //获取Mapper代理对象工厂 Start
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {//不存在直接抛异常
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    //获取Mapper代理对象工厂 End
    try {
        return mapperProxyFactory.newInstance(sqlSession);//创建代理对象
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

这里的代码其实我们在前文已经介绍过了,具体请看: 3.4 MyBatis基础支持层-绑定模块、缓存模块 一 模块绑定 部分。所以,最终会创建Mapper接口的代理对象MapperProxy。我们再回顾一下MapperProxy.invoke() 方法(这里不做细讲,只做流程介绍):

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {//如果目标方法的类是一个具体的实现类, 则直接调用
            return method.invoke(this, args);
        } else {//目标方法的类是一个接口或其他情况
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}

通常我们的Mapper不会是一个Object,而是一个interface,所以会走到cacheInvoker()获取MapperMethodInvoker

/**
* 缓存Method对应的MapperMethodInvoker
* @param method Method
* @return MapperMethodInvoker
* @throws Throwable 异常
*/
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
        //先从缓存中获取 Start
        MapperMethodInvoker invoker = methodCache.get(method);
        if (invoker != null) {
            return invoker;
        }
        //先从缓存中获取 End

        return methodCache.computeIfAbsent(method, m -> {
            if (m.isDefault()) {//是否是默认的方法
                try {
                    if (privateLookupInMethod == null) {
                        return new DefaultMethodInvoker(getMethodHandleJava8(method));
                    } else {
                        return new DefaultMethodInvoker(getMethodHandleJava9(method));
                    }
                } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                        | NoSuchMethodException e) {
                    throw new RuntimeException(e);
                }
            } else {//非默认的方法
                return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
            }
        });
    } catch (RuntimeException re) {
        Throwable cause = re.getCause();
        throw cause == null ? re : cause;
    }
}

而我们业务Mapper的增删改查的方法,一般也不是default,而是一个具体的方法,所以最终会创建PlainMethodInvoker,然后调用其invoke()方法执行真正的增删改查的逻辑。

三 执行

通过上文分析,以我们平时使用MyBatis为例,通常我们的Mapper是一个interface,并且其中封装的方法并不是default的,所以MapperProxy中封装的其实是PlainMethodInvoker,我们看PlainMethodInvoker.invoke() 方法:

@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return mapperMethod.execute(sqlSession, args);//调用execute执行
}
/**
* 执行操作
* @param sqlSession SqlSession
* @param args 实参列表
* @return 执行结果
*/
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {//根据Sql语句的类型调用sqlSession指定的方法
        case INSERT: {//INSERT插入数据
            Object param = method.convertArgsToSqlCommandParam(args);
            //调用sqlSession.insert()方法
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
        }
        case UPDATE: {//UPDATE更新数据
            Object param = method.convertArgsToSqlCommandParam(args);
            //调用sqlSession.update()方法
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
        }
        case DELETE: {//DELETE删除数据
            Object param = method.convertArgsToSqlCommandParam(args);
            //调用sqlSession.delete()方法
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
        }
        case SELECT://SELECT查询数据
            if (method.returnsVoid() && method.hasResultHandler()) {//返回值是void且ResultSet通过ResultHandler处理的方法
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {//返回值是Collection或者数组
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {//返回值是Map
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {//返回值是Cursor(游标)
                result = executeForCursor(sqlSession, args);
            } else {//处理返回值为单一对象
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
                if (method.returnsOptional()
                        && (result == null || !method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH://FLUSH
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
        throw new BindingException("Mapper method '" + command.getName()
                + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
}

而MapperMethod.execute()我们在前文已经介绍过了(具体请看: 3.4 MyBatis基础支持层-绑定模块、缓存模块 1.3.3 MapperMethod执行)。我们以查询返回单一对象为例,其他可自行研究,会走到SqlSession.selectOne() 方法,这里的SqlSession就是我们上文中介绍的DefaultSqlSession:

@Override
public <T> T selectOne(String statement, Object parameter) {
    List<T> list = this.selectList(statement, parameter);//获取一个List
    if (list.size() == 1) {//只有一条
        return list.get(0);
    } else if (list.size() > 1) {//多条, 抛出异常
        throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {//为空
        return null;
    }
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
/**
* 查询一个集合
* @param statement SQL语句
* @param parameter 参数
* @param rowBounds 在statement的SQL语句后面执行rowBounds参数
* @param <E> 类型
* @return List集合
*/
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        MappedStatement ms = configuration.getMappedStatement(statement);//获取MappedStatement
        return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);//执行查询
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

这里从Configuration对象中获取MappedStatement对象,然后调用executor.query() 方法执行查询,我们看BaseExecutor.query()

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);//获取BoundSql对象
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);//创建一级缓存Key
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);//执行查询
}

首先获取BoundSql,BoundSql其实就是具体的SQL对象,然后创建一级缓存用的CacheKey对象,最后调用BaseExecutor.query()方法执行查询。

3.1 获取BoundSql对象

BoundSql对象就是获取最终的SQL对象,MappedStatement.getBoundSql() 代码如下:

/**
* 获取BoundSql独享
* @param parameterObject 参数
* @return BoundSql
*/
public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);//获取BoundSql对象
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();//参数的映射
    if (parameterMappings == null || parameterMappings.isEmpty()) {
        boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    for (ParameterMapping pm : boundSql.getParameterMappings()) {
        String rmId = pm.getResultMapId();
        if (rmId != null) {
            ResultMap rm = configuration.getResultMap(rmId);
            if (rm != null) {
                hasNestedResultMaps |= rm.hasNestedResultMaps();
            }
        }
    }

    return boundSql;
}

所以,这里主要的逻辑是在sqlSource.getBoundSql()方法,而SqlSource对象其实我们在 4.2 MyBatis标签解析-解析映射配置文件 1.9.4 创建SqlSource 部分已经简单介绍过了,对于动态的Sql封装成DynamicSqlSource对象,而非动态SQL封装成RawSqlSource对象了。对于非动态SQL我这里不做介绍了,这里看DynamicSqlSource对象:

/**
* 通过解析得到BoundSql对象, BoundSql封装了包含"?"占位符的SQL语句以及绑定的实参
* @param parameterObject parameterType对应的对象
* @return BoundSql
*/
@Override
public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);//创建DynamicContext对象
    rootSqlNode.apply(context);//执行对应节点(if节点、trim节点等)
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);//创建SqlSourceBuilder对象
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();//参数的Class
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());//解析并获取SqlSource对象
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);//获取BoundSql对象
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
}

这里的rootSqlNode.apply(),主要就是调用IfSqlNode、ChooseSqlNode、TrimSqlNode对应的apply方法。比如<if test=“”>标签,对应IfSqlNode,主要使用的是OGNL表达式进行应用的,等后续我们再单独讲解OGNL相关内容,主要就是判断,并进行SQL的处理。然后调用SqlSourceBuilder.parse() 进行解析:

/**
* 解析SqlSource
* @param originalSql 原始的SQL
* @param parameterType 参数类型
* @param additionalParameters 附加的参数
* @return SqlSource
*/
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql;
    if (configuration.isShrinkWhitespacesInSql()) {
        sql = parser.parse(removeExtraWhitespaces(originalSql));
    } else {
        sql = parser.parse(originalSql);
    }
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

这里主要对我们SQL中的#{}占位符进行解析,使用的TokenHandler是ParameterMappingTokenHandler

@Override
public String handleToken(String content) {
    parameterMappings.add(buildParameterMapping(content));
    return "?";
}

可以看出,这里使用JDBC中PreparedStatement中的"?"替换掉#{}占位符,最终创建StaticSqlSource对象。所以这里我们已经将SQL中的标签、占位符等都已经处理成相应的内容了。

3.2 创建CacheKey

CacheKey是一级缓存的Key,BaseExecutor.createCacheKey() 代码如下:

/**
* 创建缓存Key
* @param ms MappedStatement
* @param parameterObject 参数
* @param rowBounds RowBounds
* @param boundSql BoundSql
* @return CacheKey
*/
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    //已经关闭的话就证明SqlSession关闭了 Start
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    //已经关闭的话就证明SqlSession关闭了 End

    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());//将MappedStatement.id添加到CacheKey中
    cacheKey.update(rowBounds.getOffset());//将offset添加到CacheKey中
    cacheKey.update(rowBounds.getLimit());//将limit添加到CacheKey中
    cacheKey.update(boundSql.getSql());//将SQL语句添加到CacheKey中
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();//获取SQL语句绑定的参数
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();//TypeHandler注册器

    //获取用户输入的参数, 添加到CacheKey中 Start
    for (ParameterMapping parameterMapping : parameterMappings) {
        if (parameterMapping.getMode() != ParameterMode.OUT) {//过滤掉输出类型的参数
            Object value;
            String propertyName = parameterMapping.getProperty();//参数名
            if (boundSql.hasAdditionalParameter(propertyName)) {
                value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
                value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                value = parameterObject;
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                value = metaObject.getValue(propertyName);
            }
            cacheKey.update(value);//将参数添加到CacheKey中
        }
    }
    //获取用户输入的参数, 添加到CacheKey中 End

    //如果环境不为空的话则将Environment.id添加到CacheKey中 Start
    if (configuration.getEnvironment() != null) {
        cacheKey.update(configuration.getEnvironment().getId());
    }
    //如果环境不为空的话则将Environment.id添加到CacheKey中 End
    return cacheKey;
}

从这里可以看出影响CacheKey的元素有很多,参数、sql、环境等。

3.3 执行查询

最后执行查询BaseExecutor.query()

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
                         CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    //如果已经关闭则直接抛出异常 Start
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    //如果已经关闭则直接抛出异常 End

    //清空缓存(第一次查询或者配置了刷新缓存) Start
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    //清空缓存(第一次查询或者配置了刷新缓存) End

    List<E> list;
    try {
        queryStack++;
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;//查询一级缓存
        if (list != null) {//缓存不为空
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {//缓存为空查询数据库
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        queryStack--;//当前查询完成, 查询层数减少
    }
    if (queryStack == 0) {
        //延迟加载 Start
        for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        //延迟加载 End
        deferredLoads.clear();

        //缓存级别是SQL语句级别也清除缓存 Start
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            clearLocalCache();
        }
        //缓存级别是SQL语句级别也清除缓存 End
    }
    return list;
}

这里其实我们在上文中已经介绍了,对于一级缓存的详细我们稍后详细介绍。这里主要介绍从数据库中查询内容queryFromDatabase()

/**
* 从数据库查询
* @param ms MappedStatement
* @param parameter 参数
* @param rowBounds RowBounds
* @param resultHandler ResultHandler
* @param key CacheKey
* @param boundSql  BoundSql
* @param <E> 类型
* @return 结果
* @throws SQLException 异常
*/
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

核心方法委托给doQuery(),我们看SimpleExecutor.doQuery()

/**
* 执行查询
* @param ms MappedStatement
* @param parameter 参数
* @param rowBounds RowBound
* @param resultHandler 结果处理器
* @param boundSql BoundSql
* @param <E> 类型
* @return 集合
* @throws SQLException 异常
*/
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
                           BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();//获取Configuration对象
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        stmt = prepareStatement(handler, ms.getStatementLog());//准备Statement
        return handler.query(stmt, resultHandler);//执行查询并对结果集进行处理ResultSetHandler
    } finally {
        closeStatement(stmt);
    }
}

注意,这里创建StatementHandler,也存在一个拦截器:

/**
* 创建一个StatementHandler
* @param executor 执行器
* @param mappedStatement MappedStatement
* @param parameterObject 参数
* @param rowBounds RowBounds
* @param resultHandler 结果集处理器
* @param boundSql BoundSql
* @return StatementHandler
*/
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
                                            RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
            rowBounds, resultHandler, boundSql);//创建RoutingStatementHandler
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);//执行所有插件
    return statementHandler;
}

具体这里不做介绍了,上文已经分析过Plugin的执行流程了。
准备Statement代码prepareStatement():

/**
* 准备Statement
* @param handler Handler
* @param statementLog 日志
* @return Statement
* @throws SQLException 异常
*/
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);//获取连接
    stmt = handler.prepare(connection, transaction.getTimeout());//获取Statement
    handler.parameterize(stmt);
    return stmt;
}

最后handler.query(),其实主要就是调用Statement.execute()方法了,然后就是使用ResultSetHandler进行结果集的封装了,结果集的封装可自行阅读相关的代码。
MyBatis的执行流程分析到这里其实已经结束了,至于具体的一些没介绍到的细节可自行阅读,相信读者朋友也有自行阅读MyBatis源码的能力了。

0

评论区