• 『手撕 Mybatis 源码』05 - SqlSession 执行主流程


    SqlSession 执行主流程

    获取 BoundSql

    1. 经过加载完所有配置之后,继续梳理执行 sql 的过程
    public class MybatisTest {
      @Test
      public void test1() throws IOException {
    	...
    	// 4. 委派给 Executor 来执行,Executor 执行时又会调用很多其他组件(参数设置、解析 sql 的获取,sql 的执行、结果集的封装)
    	User user = sqlSession.selectOne("user.findUserById", 1);
        ...
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 当调用 selectOne() 时,底层将会调用的是 selectOne(java.lang.String, java.lang.Object)
    public class DefaultSqlSession implements SqlSession {
      ...
      @Override
      public <T> T selectOne(String statement, Object parameter) {
        // Popular vote was to return null on 0 results and throw exception on too many.
        List<T> list = this.selectList(statement, parameter);
      	...
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 继续调用时,会先装入默认的分页器 RowBounds,然后继续调用重载的 selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)
    public class DefaultSqlSession implements SqlSession {
      ...
      @Override
      public <E> List<E> selectList(String statement, Object parameter) {
        // 调用重载方法
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 继续调用,也会装入空的 ResultHandler,继续调用重载的 selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
    public class DefaultSqlSession implements SqlSession {
      ...
      @Override
      public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        // 参数4:结果集处理器
        return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 通过传入 statementId 即 “user.findUserById”,然后 configuration 对象拿到 MappedStatement 对象,然后利用执行器来执行查询
    public class DefaultSqlSession implements SqlSession {
      ...
      private final Configuration configuration;
      private final Executor executor;
      ...
      private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
      	 ...
         // 根据传入的 statementId 即 user.findUserById,获取 MappedStatement 对象
         MappedStatement ms = configuration.getMappedStatement(statement);
         // 调用执行器的查询方法
         // wrapCollection(parameter) 是用来装饰集合或者数组参数
         return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1. 在执行器执行的查询时,是调用经过封装后的 CachingExecutorquery() 方法。该方法会从 MappedStatement 对象中拿到对应的 BoundSql 对象
    public class CachingExecutor implements Executor {
      ...
      @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 获取绑定的 SQL 语句,比如 "SELECT * FROM user WHERE id = ? "
        // 其中 parameterObject 是 1
        BoundSql boundSql = ms.getBoundSql(parameterObject);
    	...
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 其中,MappedStatement 对象会将参数 parameterObject 传入,然后委托 SqlSource 来获取 BoundSql,从上面的流程下来是不涉及动态 sql,所以这个 SqlSourceStaticSqlSource
    public final class MappedStatement { 
      ...
      private SqlSource sqlSource;
      ...
      public BoundSql getBoundSql(Object parameterObject) {
        // sqlSource 是解析 SELECT id, name FROM  user WHERE id = #{id} 之后得到的源集
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        ...
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • StaticSqlSource 会把 configurationsqlparameterMappingsparameterObject 对象封装到 BoundSql 内并且返回
    public class StaticSqlSource implements SqlSource {
    
      private final String sql;
      private final List<ParameterMapping> parameterMappings;
      private final Configuration configuration;
      
      @Override
      public BoundSql getBoundSql(Object parameterObject) {
        /*
        	sql:SELECT id, name FROM  user WHERE id = ?
        	parameterMappings:ParameterMapping{property='id', mode=IN, javaType=class java.lang.Integer, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}
        	parameterObject:现在是 1
        */
        return new BoundSql(configuration, sql, parameterMappings, parameterObject);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 拿到 BoundSql 后,先检查参数是不是一个对象映射,需要获取参数对应的映射位,但是现在的映射文件的 sql 输入只是个整形,所以这里执行是空,最后 MappedStatement 对象直接返回 BoundSql 对象,然后 CachingExecutor 就拿到对应的 BoundSql
    public final class MappedStatement { 
      ...
      private SqlSource sqlSource;
      ...
      public BoundSql getBoundSql(Object parameterObject) {
      	// 拿到了 boundSql 
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings == null || parameterMappings.isEmpty()) {
          boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
        }
    
        // 这次是空,这里检查参数的映射对象,即 ResultMap
        for (ParameterMapping pm : boundSql.getParameterMappings()) {
          String rmId = pm.getResultMapId();
          ...
        }
        // 直接返回
        return boundSql;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    1. 总结
      在这里插入图片描述

    生成 CacheKey

    1. 问题
    • CacheKey 是由几部分构成的,怎么生成的?
    • 怎么完成的 CacheKey 比较,怎么保证 CacheKey 的唯一性?
    1. 对于 SqlSession 而言,它会把实际执行交给 Executor,而我们知道在 MyBatis 中会话级别是有缓存的,那么这个缓存 Key 是怎么构造,就是在 CachingExecutor 当中,根据 MappedStatementparameterObject 实际参数、rowBounds 分页对象以及 BoundSql 来构造的
    public class CachingExecutor implements Executor {
      ...
      //第一步
      @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 获取绑定的 SQL 语句,比如 "SELECT * FROM user WHERE id = ? "
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        // 生成缓存Key
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        ...
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. CachingExecutor 中,它会委派 SimpleExecutor 来创建缓存建
    public class CachingExecutor implements Executor {
    
      private final Executor delegate;
      ...
      @Override
      public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
         // delegate 这个属性就是被包装的 executor,现在是 simpleExecutor
        return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql); 
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. SimpleExecutor 首先会创建一个 CacheKey 对象,这个对象会初始化各种算子参数,用于后面的缓存计算,缓存 Key 的组成部分如下
    public class CacheKey implements Cloneable, Serializable {
    
      private static final int DEFAULT_MULTIPLIER = 37;
      private static final int DEFAULT_HASHCODE = 17;
      ...
      private final int multiplier; // 参与 hash 运算的乘数
      private int hashcode; // cachekey 的 hash 值,在 update 函数中实时算出来
      private long checksum; // 校验和,hash 值的和
      private int count; // updateList 的中的元素个数
      // 根据该集合中的元素判断两个 cacheKey 是否相同
      private List<Object> updateList;
    
      public CacheKey() {
        this.hashcode = DEFAULT_HASHCODE;
        this.multiplier = DEFAULT_MULTIPLIER;
        this.count = 0;
        this.updateList = new ArrayList<>();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. 回到 SimpleExecutor,它其实调用的是父类 BaseExecutorcreateCacheKey() 方法,会根据 MappedStatementparameterObject 实际参数、rowBounds 分页对象以及 BoundSql 来计算 CacheKey 内的哈希值
    public abstract class BaseExecutor implements Executor {
      ...
      @Override
      public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        // 创建cacheKey对象
        CacheKey cacheKey = new CacheKey();
        // id
        cacheKey.update(ms.getId());
        // 分页参数
        cacheKey.update(rowBounds.getOffset());
        cacheKey.update(rowBounds.getLimit());
        // sql
        cacheKey.update(boundSql.getSql());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        // mimic DefaultParameterHandler logic
        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);
          }
        }
        if (configuration.getEnvironment() != null) {
          // 当前环境的值也会设置
          cacheKey.update(configuration.getEnvironment().getId());
        }
        return cacheKey;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 其中 CacheKeyupdate() 方法会把输入的各种参数计算一次哈希,然后把值也存到里面去
    public class CacheKey implements Cloneable, Serializable {
      ...
      public void update(Object object) {
        // 获取参数的object的hash值
        int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
    
        // 更新count 、checksum以及hashcode的值
        count++;
        checksum += baseHashCode;
        baseHashCode *= count;
        hashcode = multiplier * hashcode + baseHashCode;
    
        // 将对象添加到list集合中
        updateList.add(object);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 生成的哈希值如下
      在这里插入图片描述
    1. 总结
    • 最后生成的 Key 找缓存值流程如下
      在这里插入图片描述
    • 生成 CacheKey 流程
      在这里插入图片描述

    缓存优先级

    1. 问题
    • 如果开启了一二级缓存,究竟是调用一级缓存优先还是二级缓存?
    1. 首先配置文件中,开启二级缓存
    <mapper namespace="user">
    
      <cache>cache>
      ...
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 拿到 CacheKeyparameterObjectMappedStatementBoundSqlRowBounds 时候,开始执行真正的查询
    public class CachingExecutor implements Executor {
      ...
      // 第一步
      @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        ...
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. query() 方法
    • 首先会从 MappedStatement 拿到二级缓存,然后检查对应的 sql 是否配置了 flushCache=true,是的话,先把二级缓存清空了
    • 然后判断对应语句是否使用缓存 useCache,默认是开启的
    • 然后根据 StatementType 确定是否处理出参参数,这里是 PREPARED 类型,不是存储过程(CALLABLE 类型),所以不处理
    • 再从二级缓存中查询数据,没有委托给 SimpleExecutor 查询一级缓存和数据库,最后把查出来的数据放回到二级缓存(暂时是存到 map 集合,实际还没存到二级缓存)
    public class CachingExecutor implements Executor {
    
      private final Executor delegate;
      private final TransactionalCacheManager tcm = new TransactionalCacheManager();
      ...
      @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
          throws SQLException {
        // 获取二级缓存,需要在配置文件中配置  标签
        //   
          if (ms.isUseCache() && resultHandler == null) { 
    		// 处理输出参数,存储过程才会处理,这里 StatementType 是 PREPARED 类型
            ensureNoOutParams(ms, boundSql);
            // 从二级缓存中查询数据
            List<E> list = (List<E>) tcm.getObject(cache, key);
            // 如果二级缓存中没有查询到数据,则查询一级缓存及数据库
            if (list == null) {
              // 委托给 BaseExecutor 执行
              list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
              // 将查询结果 要存到二级缓存中(注意:此处只是存到map集合中,没有真正存到二级缓存中)
              tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
          }
        }
        // 委托给 BaseExecutor 执行
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    1. 委托给 SimpleExecutor 父类的 query() 方法
    • 首先判断 sql 是否配置了 flushCacheRequired,是的话会在执行器执行之前,清空本地以及缓存
    • 然后从一级缓存中获取数据
    • 如果有缓存结果,再判断是否是存储过程类型(CALLABLE 类型),是的话处理输出参数,否则的话直接从数据库里面查询结果
    public abstract class BaseExecutor implements Executor {
      protected PerpetualCache localCache;
      ...
      @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());
        // 如果该执行器已经关闭,则抛出异常
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        // 1. 如果配置了 flushCacheRequired 为 true,则会在执行器执行之前就清空本地一级缓存
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
          // 1.1. 清空缓存
          clearLocalCache();
        }
        List<E> list;
        try {
          // 2. 查询堆栈 + 1
          queryStack++;
          // 从一级缓存中获取数据
          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
          if (list != null) {
            // 3.1. 已有缓存结果,则处理本地缓存结果输出参数(只有存储过程会走)
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            // 3.2. 没有缓存结果,则从数据库查询结果
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          // 查询堆栈数 -1
          queryStack--;
        }
        ...
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    1. 总结
      在这里插入图片描述

    StatementHandler 预处理得到 Statement 对象

    1. SimpleExecutor 从缓存中拿不到到数据,就需要从 db 中获取
    public abstract class BaseExecutor implements Executor {
      ...
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ...
        try {
          ...
          } else {
            // 3.2. 没有缓存结果,则从数据库查询结果
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. 然后执行 SimpleExecutordoQuery(),这是由 SimpleExecutor 实现的。其中查询前会在本地缓存中,添加占位符,拿到数据后再移除
    public abstract class BaseExecutor implements Executor {
      ...
      private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        // 1. 首先向本地缓存中存入一个 ExecutionPlaceholder 的枚举类占位 value
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
          // 2. 执行 doQuery 方法
          list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
          // 3. 执行完成移除这个 key
          localCache.removeObject(key);
        }
        // 4. 查询结果存入缓存中
        localCache.putObject(key, list);
        // 5. 如果 MappedStatement 的类型为 CALLABLE,则向 localOutputParameterCache 缓存中存入 value 为 parameter 的缓存
        if (ms.getStatementType() == StatementType.CALLABLE) {
          localOutputParameterCache.putObject(key, parameter);
        }
        return list;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    1. SimpleExecutor 先从 MappedStatement 拿到 Configuration 对象,然后通过 Configuration 对象创建 StatementHandler 语句处理器
    public class SimpleExecutor extends BaseExecutor {
      
      protected Executor wrapper;
      ...
      @Override
      public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
          // 1. 获取配置实例
          Configuration configuration = ms.getConfiguration();
          // 2. new 一个 StatementHandler 实例
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          ...
        } finally {
          // 5. 关闭statement
          closeStatement(stmt);
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. StatementHandler 创建很关键,它会先通过 RoutingStatementHandler 以及插件拦截装饰后返回 StatementHandler 对象
    • 【重点】: 这里的插件机制是 MyBatis 扩展的关键地方之一
    public class Configuration {
      ...
      protected final InterceptorChain interceptorChain = new InterceptorChain();
      ...
      public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 创建路由功能的 StatementHandler,根据 MappedStatement 中的 StatementType,可以在 sql 中配置
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        // 插件机制:对核心对象进行拦截
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 其中 StatementHandler 组织如下
      在这里插入图片描述
      • BaseStatementHandler: 基础语句处理器(抽象类),它基本把语句处理器接口的核心部分都实现了,包括配置绑定、执行器绑定、映射器绑定、参数处理器构建、结果集处理器构建、语句超时设置、语句关闭等,并另外定义了新的方法 instantiateStatement() 供不同子类实现以便获取不同类型的语句连接,子类可以普通执行 SQL 语句,也可以做预编译执行,还可以执行存储过程等
      • SimpleStatementHandler:普通语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.Statement 对象的处理,处理普通的不带动态参数运行的 SQL,即执行简单拼接的字符串语句,同时由于 Statement 的特性,SimpleStatementHandler 每次执行都需要编译 SQL (注意:我们知道 SQL 的执行是需要编译和解析的)
      • PreparedStatementHandler:预编译语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.PrepareStatement 对象的处理,相比上面的普通语句处理器,它支持可变参数 SQL 执行,由于 PrepareStatement 的特性,它会进行预编译,在缓存中一旦发现有预编译的命令,会直接解析执行,所以减少了再次编译环节,能够有效提高系统性能,并预防 SQL 注入攻击(所以是系统默认也是我们推荐的语句处理器)
      • CallableStatementHandler:存储过程处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.CallableStatement 对象的处理,很明了,它是用来调用存储过程的,增加了存储过程的函数调用以及输出/输入参数的处理支持
      • RoutingStatementHandler:路由语句处理器,直接实现了 StatementHandler 接口,作用如其名称,确确实实只是起到了路由功能,并把上面介绍到的三个语句处理器实例作为自身的委托对象而已,所以执行器在构建语句处理器时,都是直接 new 了 RoutingStatementHandler 实例
    1. RoutingStatementHandler 会根据 sql 的 statementType 选择对应处理的 handler
    public class RoutingStatementHandler implements StatementHandler {
    
      private final StatementHandler delegate;
    
      public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 可以在 Mapper.xml 对 sql 指定 statementType
        switch (ms.getStatementType()) {
          case STATEMENT:
            delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          case PREPARED:
            delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          case CALLABLE:
            delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          default:
            throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    1. 获取到 StatementHandler 之后,就需要执行第 3 步创建 Statement 对象
    public class SimpleExecutor extends BaseExecutor {
      ...
      @Override
      public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
          // 1. 获取配置实例
          Configuration configuration = ms.getConfiguration();
          // 2. new 一个 StatementHandler 实例
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          // 3. 准备处理器,主要包括创建 statement 以及动态参数的设置
          stmt = prepareStatement(handler, ms.getStatementLog());
          ...
        } finally {
          // 5. 关闭statement
          closeStatement(stmt);
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. SimpleExecutor 获取 Statement 对象前,会通过 JDBCTransaction 先获取 Connection 对象,即通过 DataSource 返回 Connection 对象。如果开启了日志调试模式,返回的 Connection 对象是经过代理的。然后 RoutingStatementHandler 做预处理得到预编译的 Statement 对象,并参数化后返回 Statement 对象
    public class SimpleExecutor extends BaseExecutor {
      
      protected Transaction transaction;
      ...
      // 这个是父类的方法
      protected Connection getConnection(Log statementLog) throws SQLException {
        Connection connection = transaction.getConnection();
        if (statementLog.isDebugEnabled()) { // 是否进行日志调试
          return ConnectionLogger.newInstance(connection, statementLog, queryStack);
        } else {
          return connection;
        }
      }
      ...
      private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // 1. 获取代理后(增加日志功能)的Connection对象
        Connection connection = getConnection(statementLog);
        // 2. 创建 Statement 对象(可能是一个 SimpleStatement,一个 PreparedStatement 或 CallableStatement)
        stmt = handler.prepare(connection, transaction.getTimeout());
        // 3. 参数化处理
        handler.parameterize(stmt);
        // 4. 返回执行前最后准备好的Statement对象
        return stmt;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    1. RoutingStatementHandler 再次委派 PreparedStatmentHandler 来处理
    public class RoutingStatementHandler implements StatementHandler {
    
      private final StatementHandler delegate;
      ...
      @Override
      public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        return delegate.prepare(connection, transactionTimeout);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. PreparedStatmentHandler 会通过拿到 BoundSql 中的 sql 之后,通过 Connection 对 sql 进行预编译,得到 PrepareStatement 并返回
    public class PreparedStatementHandler extends BaseStatementHandler {
      ...
      protected BoundSql boundSql;
      // 这个是父类 BaseStatementHandler 的方法
      @Override
      public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        ErrorContext.instance().sql(boundSql.getSql());
        Statement statement = null;
        try {
          statement = instantiateStatement(connection);
          ...
          return statement; // 获取预编译对象
        } catch (SQLException e) {
          ...
        }
      }
      ...
      @Override
      protected Statement instantiateStatement(Connection connection) throws SQLException {
        String sql = boundSql.getSql();
        if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
           ...
        } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
          return connection.prepareStatement(sql); //# 获取预编译对象
        } else {
          ...
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    1. 总结
      在这里插入图片描述

    Statement 参数化设置

    1. 上面已经阐述到 SimpleExecutor 获取预编译 Statement 对象,下面继续看看参数化的过程,下面会继续交由 RoutingStatementHandlerStatement 对象参数化处理
    public class SimpleExecutor extends BaseExecutor {
      ...
      private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        ...
        // 3. 参数化处理
        handler.parameterize(stmt);
        // 4. 返回执行前最后准备好的Statement对象
        return stmt;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. RoutingStatementHandler 委派 PreparedStatementHandler 来执行参数设置,内部调用的是 ParameterHandler 来处理参数化
    public class PreparedStatementHandler extends BaseStatementHandler {
      
      protected final ParameterHandler parameterHandler;
      ...
      @Override
      public void parameterize(Statement statement) throws SQLException {
        parameterHandler.setParameters((PreparedStatement) statement);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. ParameterHandler 参数化处理流程
    • 首先从 BoundSql 中,拿到参数化映射列表 ParameterMappings,这个列表是有序的,因为都是从原来的 sql 中按序提取的,然后遍历这个表
    • 拿到这个表之后,取到属性名后,进行一系列校验,包括查看 parameterObject 是不是为空,TypeHandlerRegistry 有没有对应参数的处理方法
    • ParameterMapping 拿到对应 TypeHandlerJdbcType,然后通过 TypeHandler 来参数化
    • 最后把参数化后的 Statement 对象返回到 SimpleExecutor
    public class DefaultParameterHandler implements ParameterHandler {
      // 持有 typeHandler 注册器
      private final TypeHandlerRegistry typeHandlerRegistry;
      // 持有MappedStatement实例,这是一个静态的xml的一个数据库操作节点的静态信息而已
      private final MappedStatement mappedStatement;
      // 当前实际执行前的参数对象
      private final Object parameterObject;
      // 动态语言被执行后的结果 sql
      private final BoundSql boundSql;
      private final Configuration configuration;
      ...
      
      @Override
      public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        // 1. 获取boundSql中的参数映射信息列表
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
          // 1.1. 遍历参数映射列表,这个列表信息就是我们xml文件中定义的某个查询语句的所有参数映射信息,注意这个List中的参数映射元素的顺序是和真实xml中sql的参数顺序对应的
          for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            // 1.2. 只有入参类型才会设置 PreparedStatement
            if (parameterMapping.getMode() != ParameterMode.OUT) {
              Object value;
              // 取出参数名,这里比如说是'id'
              String propertyName = parameterMapping.getProperty();
              if (boundSql.hasAdditionalParameter(propertyName)) {
              	...
               // 有没有对应的这个类型转换处理器
              } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                value = parameterObject;
              } else {
                // 1.3. 这一步的工作就是从当前实际传入的参数中获取到指定key('id')的value值,比如是'15800000000'
                // 经过测试,其实就是从 map 或者 对象,拿到这个对应的值
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                value = metaObject.getValue(propertyName);
              }
    
              // 2. 获取该参数对应的typeHandler
              TypeHandler typeHandler = parameterMapping.getTypeHandler();
    
              // 2.1. 获取该参数对应的jdbcType
              JdbcType jdbcType = parameterMapping.getJdbcType();
              if (value == null && jdbcType == null) {
                jdbcType = configuration.getJdbcTypeForNull();
              }
              try {
                // 3. 重点是调用每个参数对应的 typeHandler 的 setParameter 方法为该ps设置正确的参数值
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
              } catch (TypeException | SQLException e) {
                throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
              }
            }
          }
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    1. 拿到参数化的 Statement 对象后,通过 RoutingStatementHandler 真正执行查询
    public class SimpleExecutor extends BaseExecutor {
      ...
      @Override
      public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
          ...
          // 2
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          ...
          // 4. 执行真正的数据库操作调用
          return handler.query(stmt, resultHandler);
        } finally {
          // 5. 关闭statement
          closeStatement(stmt);
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1. RoutingStatementHandler 再次委派 PreparedStatementHandler 执行查询。PreparedStatementHandler 拿到 PreparedStatement 后,直接执行查询。然后通过 ResultSetHandler 处理结果集,最后返回查询结果
    public class PreparedStatementHandler extends BaseStatementHandler {
    
      protected final ResultSetHandler resultSetHandler;
      ...
      @Override
      public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        // 结果集处理
        return resultSetHandler.handleResultSets(ps);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. 总结
      在这里插入图片描述

    解析结果集

    1. 问题
    • 什么逻辑来完成的查询结果处理
    1. 继续回到 PreparedStatementHandler 查询执行,经过执行查询获得结果集之后,需要 ResultSetHandler 对查询结果进行处理
    public abstract class BaseStatementHandler implements StatementHandler {
      
      protected final ResultSetHandler resultSetHandler;
      ...
      @Override
      public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        // 1. 结果集处理
        return resultSetHandler.handleResultSets(ps);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. ResultSetHandler 实际的实现是 DefaultResultSetHandler,处理的流程如下
    • 首先对 Statement 获取第一个结果集,并且把结果集包装成 ResultSetWrapper,它会把结果集的 columnNames、classNames、jdbcTypes 属性包装到里面
    • 通过 MappedStatement 拿到 Mapper.xml 在 sql 语句中配置的、所有要映射的 ResultMap
    • 然后遍历所有的 ResultMap,根据 ResultSetWrapper 映射的所有结果集存放到 multipleResults 局部变量集合中
    • 最后返回 multipleResults,如果只有一个结果集,就从 multipleResults 取出第一个
    public class DefaultResultSetHandler implements ResultSetHandler {
    
      private final MappedStatement mappedStatement;
      private final Configuration configuration;
      ...
      private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    
        ResultSet rs = stmt.getResultSet();
        while (rs == null) {
        
          if (stmt.getMoreResults()) {
            rs = stmt.getResultSet();
          } else {
            if (stmt.getUpdateCount() == -1) {
              break;
            }
          }
        }
        // 包装结果集
        return rs != null ? new ResultSetWrapper(rs, configuration) : null;
      }
      
      // HANDLE RESULT SETS
      // 将 Statement 执行后产生的结果集(可能有多个结果集)映射为结果列表
      // (1)获取到 ResultSet 结果集对象  (2)获取映射关系  (3)根据映射关系封装实体
    
      @Override
      public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    
        // 创建结果容器
        final List<Object> multipleResults = new ArrayList<>();
    
        int resultSetCount = 0;
        // 1. 这里是获取第一个结果集,将传统 JDBC 的 ResultSet 包装成一个包含结果列元信息的 ResultSetWrapper 对象
        ResultSetWrapper rsw = getFirstResultSet(stmt);
    
        // 2. 这里是获取所有要映射的 ResultMap(按照逗号分割出来的),例如在 Mapper.xml 配置的 
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        // 要映射的 ResultMap 的数量
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount);
        // 3. 循环处理每个 ResultMap,从第一个开始处理
        while (rsw != null && resultMapCount > resultSetCount) {
          // 得到结果映射信息(取出第一个结果集)
          ResultMap resultMap = resultMaps.get(resultSetCount);
          // 4. 根据映射规则对结果集进行 pojo 转化(最后放入 multipleResults 结果集中)
          handleResultSet(rsw, resultMap, multipleResults, null);
          // 处理下个结果集
          rsw = getNextResultSet(stmt);
          cleanUpAfterHandlingResultSet();
          resultSetCount++;
        }
    
        // 对应