Mybatis源码(八):查询执行流程

发布时间 2023-03-22 21:10:48作者: 无虑的小猪


  Mybatis源码(七):SQL执行流程中已提到,根据不同的sqlCommandType执行不同类型的SQL,下面来看看调用Mapper接口的查询,Mybatis中做了哪些处理。

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 查询流程
User user02 = mapper.selectUserById("101");

  查询流程最终会执行到DefaultSqlSession的selectList()方法,DefaultSqlSession#selectList() 核心伪代码

1 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
2     // 根据statement id找到对应的MappedStatement
3     MappedStatement ms = configuration.getMappedStatement(statement);
4     // 如果 cacheEnabled = true(默认),Executor会被 CachingExecutor装饰
5     return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
6 }

1、获取MappedStatement对象

  根据statementid(sql标识id)从全局配置对象configuration中拿到MappedStatement,MappedStatement对象中有Mapper.xml中标签的所有属性,如id、statementType、sqlSource、useCache、入参、出参等等。

2、SQL查询执行流程

  Executor是在创建sqlSession对象时创建的,在创建BaseExecutor/ReuseExecutor/BatchExecutor之后,根据缓存配置判断是否对Executor进行对象装饰。

1 // 默认启用缓存
2 protected boolean cacheEnabled = true;
3 
4 if (cacheEnabled) {
5   executor = new CachingExecutor(executor);
6 }

  若显示的在settings中配置cacheEnabled=false,会直接调用BaseExecutor的query方法。若未配置或者显示的在settings中配置cacheEnabled=true,会优先执行CachingExecutor中的逻辑,之后再调用BaseExecutor中的逻辑。

  CachingExecutor#query() 核心代码:

1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
2   // 创建BoundSql对象,包含目标执行sql、参数信息
3   BoundSql boundSql = ms.getBoundSql(parameterObject);
4   // 创建CacheKey对象,用来命中缓存
5   CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
6   // 查询
7   return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
8 }

1、创建BoundSql对象

  StaticSqlSource#getBoundSql() 核心代码

public BoundSql getBoundSql(Object parameterObject) {
 return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}

  BoundSql对象中包含全局配置对象、xml中的sql,xml中sql的占位符#{},sql参数。

2、创建CacheKey对象

  若全局配置中开启了缓存,会根据CacheKey来判断要执行的SQL是否已经被缓存,怎样判断相同的SQL呢?下面来看CaacheKey对象

  创建缓存key,BaseExecutor#createCacheKey() 核心方法:

 1 public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
 2   // 检测当前executor是否已经关闭
 3   if (closed) {
 4     throw new ExecutorException("Executor was closed.");
 5   }
 6   // 创建CacheKey对象
 7   CacheKey cacheKey = new CacheKey();
 8   // 查询的目标执行方法全路径限定名,并添加到cacheKey对象中
 9   cacheKey.update(ms.getId());
10   // 分页起始位置,并添加到cacheKey对象中
11   cacheKey.update(rowBounds.getOffset());
12   // 每页记录数,并添加到cacheKey对象中
13   cacheKey.update(rowBounds.getLimit());
14   // 目标执行sql添加到cacheKey对象中
15   cacheKey.update(boundSql.getSql());
16   List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); // sql中的变量参数映射
17   TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); // 获取类型处理注册器
18   // mimic DefaultParameterHandler logic
19   // 获取用户传入的实参,并添加到CacheKey对象中
20   for (ParameterMapping parameterMapping : parameterMappings) {
21     if (parameterMapping.getMode() != ParameterMode.OUT) {
22       Object value;
23       String propertyName = parameterMapping.getProperty(); // 获取xml文件中SQL中的变量#{?}
24       if (boundSql.hasAdditionalParameter(propertyName)) {
25         value = boundSql.getAdditionalParameter(propertyName);
26       } else if (parameterObject == null) {
27         value = null;
28       } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { // 用户传入实参是否有类型处理器
29         value = parameterObject;
30       } else {
31         MetaObject metaObject = configuration.newMetaObject(parameterObject);
32         value = metaObject.getValue(propertyName);
33       }
34       // 将实参添加到CacheKey对象中
35       cacheKey.update(value);
36     }
37   }
38   // 如果environment的id不为空,则将其添加到CacheKey中
39   if (configuration.getEnvironment() != null) {
40     // issue #176
41     cacheKey.update(configuration.getEnvironment().getId());
42   }
43   return cacheKey;
44 }

  Mybatis中,拥有相同的方法、翻页偏移、SQL、参数值、数据源环境会被认为是同一个SQL查询。

 1   // 创建CacheKey对象
 2   CacheKey cacheKey = new CacheKey();
 3   // 查询的目标执行方法全路径限定名,并添加到cacheKey对象中
 4   cacheKey.update(ms.getId());
 5   // 分页起始位置,并添加到cacheKey对象中
 6   cacheKey.update(rowBounds.getOffset());
 7   // 每页记录数,并添加到cacheKey对象中
 8   cacheKey.update(rowBounds.getLimit());
 9   // 目标执行sql添加到cacheKey对象中
10   cacheKey.update(boundSql.getSql());
11   // 将实参添加到CacheKey对象中
12   cacheKey.update(value);
13   // environment环境信息
14   cacheKey.update(configuration.getEnvironment().getId());

  CacheKey#update() 核心代码:

 1 private static final int DEFAULT_MULTIPLYER = 37;
 2 private static final int DEFAULT_HASHCODE = 17;
 3 
 4 // 用于判重
 5 private int hashcode;
 6 private long checksum;
 7 private int count;
 8 // 存储sql信息,用于生成hashcode
 9 private List<Object> updateList;
10 
11 // 构造函数
12 public CacheKey() {
13   this.hashcode = DEFAULT_HASHCODE;
14   this.multiplier = DEFAULT_MULTIPLYER;
15   this.count = 0;
16   this.updateList = new ArrayList<>();
17 }
18 
19 // CacheKey设置进updateList属性中
20 public void update(Object object) {
21   int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
22 
23   count++;
24   checksum += baseHashCode;
25   baseHashCode *= count;
26 
27   // 每添加一个object,都重新生成hashCode
28   // 生成hashCode用于快速判重,判断是否为同一个CacheKey,重写了Object的equals方法
29   hashcode = multiplier * hashcode + baseHashCode;
30 
31   // 将值设置进updateList中
32   updateList.add(object);
33 }

  hashCode()是Object中的一个本地方法,通过随机数算法生成,返回自己生成的hashCode。

  在生成CacheKey的时候(update方法),每添加一个参数就会重新生成CacheKey的hashCode,hashcode是用乘法哈希生成的(基数baseHashCode=17,乘法因子multiplier=37)。

  CacheKey对hashCode()方法进行了重写,若哈希值(乘法哈希)、校验值(加法哈希)、要素个数任何一个不相等,都不是同一个查询,最后循环比较要素,防止哈希碰撞。

 1 // 重写equals方法
 2 public boolean equals(Object object) {
 3   if (this == object) {
 4     return true;
 5   }
 6   if (!(object instanceof CacheKey)) {
 7     return false;
 8   }
 9 
10   final CacheKey cacheKey = (CacheKey) object;
11 
12   if (hashcode != cacheKey.hashcode) {
13     return false;
14   }
15   if (checksum != cacheKey.checksum) {
16     return false;
17   }
18   if (count != cacheKey.count) {
19     return false;
20   }
21 
22   for (int i = 0; i < updateList.size(); i++) {
23     Object thisObject = updateList.get(i);
24     Object thatObject = cacheKey.updateList.get(i);
25     if (!ArrayUtil.equals(thisObject, thatObject)) {
26       return false;
27     }
28   }
29   return true;
30 }

CacheKey详情如下:

截图.png

3、执行查询SQL

  CacheKey生成之后,执行SQL查询,CachingExecutor#query() 核心代码:

 1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
 2     throws SQLException {
 3   // 获取查询语句所在命名空间对应的二级缓存
 4   Cache cache = ms.getCache();
 5   // 是否开启了二级缓存
 6   if (cache != null) {
 7     // 根据select节点的配置,决定是否需要清空二级缓存
 8     flushCacheIfRequired(ms);
 9     // 检测SQL节点的useCache配置以及是否使用了resultHandler配置
10     if (ms.isUseCache() && resultHandler == null) {
11       // 二级缓存不能保存输出类型的参数,如果查询操作调用了包含输出参数的存储过程,则报错
12       ensureNoOutParams(ms, boundSql);
13       @SuppressWarnings("unchecked")
14       // 查询二级缓存
15       List<E> list = (List<E>) tcm.getObject(cache, key);
16       if (list == null) {
17         // 二级缓存没有相应的结果对,调用封装的Executor对象的query方法
18         list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
19         // 将查询结果保存到TransactionalCache.entriesToAddOnCommit集合中
20         tcm.putObject(cache, key, list); // issue #578 and #116
21       }
22       return list;
23     }
24   }
25   // 没有启动二级缓存,直接调用底层Executor执行数据库查询操作
26   return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
27 }

  优先判断缓存开关是否打开,若缓存开关打开,根据CacheKey判断是否命中二级缓存,未命中二级缓存,调用CachingExecutor持有的Executor对象的query方法。

  调用CachingExecutor父类BaseExecutor的query()方法,BaseExecutor#query() 核心伪代码

 1 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 2   ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
 3   // 检测当前Executor是否已经关闭
 4   if (closed) {
 5     throw new ExecutorException("Executor was closed.");
 6   }
 7    // 非嵌套查询,并且select节点配置的flushCache属性为true时,才会清空一级缓存
 8   if (queryStack == 0 && ms.isFlushCacheRequired()) {
 9     clearLocalCache();
10   }
11   List<E> list;
12   try {
13     // 增加查询层数
14     queryStack++;
15     // 查询一级缓存
16     list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
17     // 针对存储过程调用的处理,在一级缓存命中时,获取缓存中保存的输出类型参数,并设置到用户传入的实参对象中
18     if (list != null) {
19       handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
20     // 调用doQuery方法完成数据库查询,并得到映射后的结果对象
21     } else {
22       list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
23     }
24   } finally {
25     // 当前查询完成,查询层数减少
26     queryStack--;
27   }
28   // ..
29   return list;
30 }

  根据CacheKey判断是否命中一级缓存,若命中一级缓存则从缓存中获取;若未命中一级缓存,执行queryFromDatabase()方法查询数据库。

  BaseExecutor#queryFromDatabase() 核心方法:

 1 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 2   List<E> list;
 3   // 在一级缓存中添加占位符
 4   localCache.putObject(key, EXECUTION_PLACEHOLDER);
 5   try {
 6     // 完成数据库查询操作,并返回结果对象,默认Simple
 7     list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
 8   } finally {
 9     // 删除缓存占位符
10     localCache.removeObject(key);
11   }
12   // 将真正的结果对象添加到一级缓存中
13   localCache.putObject(key, list);
14   // 存储过程的调用处理
15   if (ms.getStatementType() == StatementType.CALLABLE) {
16     // 缓存输出类型的参数
17     localOutputParameterCache.putObject(key, parameter);
18   }
19   return list;
20 }

SimpleExecutor#doQuery() 核心代码

 1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
 2   Statement stmt = null;
 3   try {
 4     // 获取全局配置对象configuration
 5     Configuration configuration = ms.getConfiguration();
 6     // 创建StatementHandler对象,实际返回的是RoutingStatementHandler对象
 7     StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
 8     // 完成SQL中变量值的替换,获取完整的sql查询语句
 9     stmt = prepareStatement(handler, ms.getStatementLog());
10     return handler.query(stmt, resultHandler);
11   } finally {
12     closeStatement(stmt);
13   }
14 }

3.1、StatementHandler
  configuration.newStatementHandler()创建一个RoutingStatementHandler对象,并向StatementHandler中植入插件。

1 //创建语句处理器
2 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
3   //创建路由选择语句处理器,根据statementType创建不同类型的StatementHandler
4   StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
5   //  植入插件
6   statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
7   return statementHandler;
8 }

  RoutingStatementHandler构造函数详情如下:

 1 // 封装的真正的StatementHandler对象
 2 private final StatementHandler delegate;
 3 
 4 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 5   // 根据MappedStatement的配置,生成一个对应的StatementHandler对象,并设置到delegate字段中
 6   switch (ms.getStatementType()) {  
 7     case STATEMENT:
 8       delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
 9       break;
10     case PREPARED:
11       delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
12       break;
13     case CALLABLE:
14       delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
15       break;
16     default:
17       throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
18   }
19 }

  根据同的MappedStatement中的statementType属性,判断要创建何种类型的StatementHandler,默认为PREPARED。statementType设值的位置,详见Mybatis 源码(四):Mapper的解析工作
  StatementType详情如下:

1 public enum StatementType {
2   // STATEMENT 对应 SimpleStatementHandler
3   // PREPARED 对应 PreparedStatementHandler
4   // CALLABLE 对应 CallableStatementHandler
5   STATEMENT, PREPARED, CALLABLE
6 }

  在创建PreparedStatementHandler对象时,调用父类BaseStatementHandler的构造方法,初始化了参数处理器parameterHandler,返回结果处理器resultSetHandler。

 1 // 将结果集映射成结果对象
 2 protected final ResultSetHandler resultSetHandler;
 3 // 参数处理器,SQL语句占位符设置为实参
 4 protected final ParameterHandler parameterHandler;
 5 
 6 protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 7   this.configuration = mappedStatement.getConfiguration();
 8   this.executor = executor;
 9   this.mappedStatement = mappedStatement;
10   this.rowBounds = rowBounds;
11 
12   this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
13   this.objectFactory = configuration.getObjectFactory();
14 
15   if (boundSql == null) { // issue #435, get the key before calculating the statement
16     generateKeys(parameterObject);
17     boundSql = mappedStatement.getBoundSql(parameterObject);
18   }
19 
20   this.boundSql = boundSql;
21   
22   // 创建parameterHandler并完成赋值
23   this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
24   // 创建resultSetHandler并完成赋值
25   this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
26 }

  参数处理器parameterHandler,主要为SQL语句绑定实参,用传入的实参替换SQL语句中的占位符?

  返回值处理器resultSetHandler,主要将结果集映射为结果对象。

1、参数处理器

  Mybatis中参数处理器默认为DefaultParameterHandler,在创建参数处理器后,可植入插件做拦截处理。
  configuration#newParameterHandler() 核心代码
1 // 创建参数处理器
2 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
3   // 创建ParameterHandler
4   ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
5   // 设置插件
6   parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
7   return parameterHandler;
8 }

  DefaultParameterHandler详情如下:

截图.png

2、返回值处理器

  Mybatis中结果集处理器默认为DefaultResultSetHandler,在设置结果集处理器时,可植入插件做拦截处理。
1 // 创建结果集处理器
2 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
3     ResultHandler resultHandler, BoundSql boundSql) {
4   //创建DefaultResultSetHandler
5   ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
6   resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
7   return resultSetHandler;
8 }

  DefaultResultSetHandler详情如下:

  截图.png

3.2、Statement -> SQL语句处理

  SQL语句处理,主要完成三件事:获取数据库连接、创建数据库执行对象Statement、使用Statement对象替换SQL中的占位符?。

  SimpleExecutor#prepareStatement() 核心代码:

 1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
 2   Statement stmt;
 3   // 获取JBDC连接
 4   Connection connection = getConnection(statementLog);
 5   // 创建Statement对象
 6   stmt = handler.prepare(connection, transaction.getTimeout());
 7   // 处理SQL中的占位符1
 8   handler.parameterize(stmt);
 9   return stmt;
10 }

1、获取数据库连接

  BaseExecutor#getConnection() 核心代码:

 1 // Transaction对象,实现事务的提交、回滚和关闭操作
 2 protected Transaction transaction;
 3 
 4 protected Connection getConnection(Log statementLog) throws SQLException {
 5   Connection connection = transaction.getConnection();
 6   if (statementLog.isDebugEnabled()) {
 7     // 若需要打印日志,返回一个ConnectionLogger(代理模式, AOP思想)
 8     return ConnectionLogger.newInstance(connection, statementLog, queryStack);
 9   } else {
10     // 不需要打印日志,直接返回连接
11     return connection;
12   }
13 }

  在全局配置中事务管理器设置的为JDBC。

<transactionManager type="JDBC"/>

  在类型初始化configuration时,别名注册器中对JDBC别名映射的对象为JdbcTransactionFactory,通过事务工厂创建事务对象JdbcTransaction。

typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);

  从事务中获取连接,JdbcTransaction#openConnection() 核心代码:

1 protected void openConnection() throws SQLException {
2   connection = dataSource.getConnection();
3   if (level != null) {
4     connection.setTransactionIsolation(level.getLevel());
5   }
6   setDesiredAutoCommit(autoCommit);
7 }

  从数据源中获取连接,并设置连接的自动提交标识,默认为false; 若指定了事务隔离级别,则设置连接的隔离级别。

2、创建Statement对象

  BaseStatementHandler#prepare() 核心伪代码

 1 public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
 2   ErrorContext.instance().sql(boundSql.getSql());
 3   Statement statement = null;
 4   // 实例化Statement
 5   statement = instantiateStatement(connection);
 6   // 设置超时
 7   setStatementTimeout(statement, transactionTimeout);
 8   // 设置读取条数
 9   setFetchSize(statement);
10   return statement;
11 }
1、实例化Statement对象

  实例化Statement对象,PreparedStatementHandler#instantiateStatement() 核心代码

 1 protected Statement instantiateStatement(Connection connection) throws SQLException {
 2   // 获取待执行的SQL语句
 3   String sql = boundSql.getSql();
 4   // 根据keyGenerator字段的值,创建PreparedStatement对象
 5   if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
 6     String[] keyColumnNames = mappedStatement.getKeyColumns();
 7     if (keyColumnNames == null) {
 8       // 返回数据库生成的主键
 9       return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
10     } else {
11       // 在insert语句执行完成之后,会将keyColumnNames指定的列返回
12       return connection.prepareStatement(sql, keyColumnNames);
13     }
14   } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
15     // 创建普通的PreparedStatement对象
16     return connection.prepareStatement(sql);
17   } else {
18     // 设置结果集是否可以滚动以及其游标是否可以上下移动,设置结果集是否可更新
19     return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
20   }
21 }

  在解析Mapper配置,MapperBuilderAssistant#addMappedStatement()中创建MappedStatement的内部类Builder时,会将MappedStatement中resultSetType属性设置为ResultSetType.DEFAULT。

  根据及resultSetType类型,通过数据库连接创建PreparedStatement对象。

2、PreparedStatement属性填充

  设置PreparedStatement的超时时间及匹配数据条数。

3、SQL语句占位符处理

  DefaultParameterHandler#setParameters() 核心伪代码:

 1 // 用户传入的实参对象
 2 private final Object parameterObject;
 3 
 4 public void setParameters(PreparedStatement ps) {
 5   ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
 6   // 取出sql中的参数映射列表
 7   List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
 8   // 检测parameterMappings集合是否为空
 9   if (parameterMappings != null) {
10     for (int i = 0; i < parameterMappings.size(); i++) {
11       ParameterMapping parameterMapping = parameterMappings.get(i);
12       // 过滤掉存储过程中的输出参数
13       if (parameterMapping.getMode() != ParameterMode.OUT) {
14         // 记录绑定的实参
15         Object value;
16         // 获取参数名称
17         String propertyName = parameterMapping.getProperty();
18         // ...
19         if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
20           // 实参可以直接通过TypeHandler转换成JdbcType
21           value = parameterObject;
22         }
23         // ...
24         // 获取parameterMapping中设置的TypeHandler对象
25         TypeHandler typeHandler = parameterMapping.getTypeHandler();
26         JdbcType jdbcType = parameterMapping.getJdbcType();
27         if (value == null && jdbcType == null) {
28           //不同类型的set方法不同,所以委派给子类的setParameter方法
29           jdbcType = configuration.getJdbcTypeForNull();
30         }
31         // 调用PreparedStatement.set*方法为SQL语句绑定相应的实参
32         typeHandler.setParameter(ps, i + 1, value, jdbcType);        
33       }
34     }
35   }
36 }

  获取sql中的参数映射表,包含了参数值Mapper.xml中映射SQL中的参数名称#{id},及参数类型。

截图.png

  遍历参数映射表,类型处理器注册器中是否有处理器可以处理用户传入的实参类型,TypeHandlerRegistry#hasTypeHandler() 核心代码:

1 // 判断类型处理注册器中的类型处理器是否可以处理javaType、jdbcType
2 public boolean hasTypeHandler(Class<?> javaType, JdbcType jdbcType) {
3   return javaType != null && getTypeHandler((Type) javaType, jdbcType) != null;
4 }

  TypeHandlerRegistry#getTypeHandler() 核心代码:

 1 private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
 2   // 查找或初始化Java类型对应的TypeHandler集合
 3   Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
 4   TypeHandler<?> handler = null;
 5   if (jdbcHandlerMap != null) {
 6     // 根据JdbcType类型查找TypeHandler对象
 7     handler = jdbcHandlerMap.get(jdbcType);
 8     if (handler == null) {
 9       handler = jdbcHandlerMap.get(null);
10     }
11     if (handler == null) {
12       // 如果jdbcHandlerMap只注册了一个TypeHandler,则使用此TypeHandler对象
13       handler = pickSoleHandler(jdbcHandlerMap);
14     }
15   }
16   // type drives generics here
17   return (TypeHandler<T>) handler;
18 }

  根据用户传入参数的Class、JdbcType获取缓存中的TypeHandler。Mybatis中所有的类型处理器都在TypeHandlerRegistry的TYPE_HANDLER_MAP属性中。

  TypeHandlerRegistry#getJdbcHandlerMap 核心伪代码:

 1 // 记录了Java类型向指定JdbcType转换时,需要使用TypeHandler对象
 2 private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<>();
 3 // 空TypeHandler集合
 4 private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
 5 
 6 private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
 7   // 查找指定Java类型对应TypeHandler集合
 8   Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
 9   // 检测是否为空集合标识
10   if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
11     return null;
12   }
13   // ...
14   TYPE_HANDLER_MAP.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
15   return jdbcHandlerMap;
16 }

  getJdbcHandlerMap()方法,根据传入实参Class,从TypeHandlerRegistry中TYPE_HANDLER_MAP属性中获取对应的类型处理器,若未匹配到,并且TYPE_HANDLER_MAP为空集合,返回null;若根据Class类型未匹配到TYPE_HANDLER_MAP中的类型处理器,将Class类型对应的类型处理器设置为空集合,返回获取到的类型处理器集合,最后再根据jdbcType的类型锁定目标参数类型处理器。

  在TypeHandlerRegistry的构造函数中,完成了常用Class类型处理器的注册。

  类型处理器集合示例如下:

  截图.png

  最后调用类型处理器基类BaseTypeHandler中的setParameter,BaseTypeHandler#setParameter() 核心伪代码:

 1 public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
 2   if (parameter == null) {
 3     if (jdbcType == null) {
 4       throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
 5     }
 6     ps.setNull(i, jdbcType.TYPE_CODE);
 7   } else {
 8     setNonNullParameter(ps, i, parameter, jdbcType);
 9   }
10 }

  当用户传入实参不为空时,调用子类的类型处理器setNonNullParameter方法,不同的类型处理器会设置不同的参数类型。如String类型处理器,StringTypeHandler#setNonNullParameter() 核心代码:

1 public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
2     throws SQLException {
3   ps.setString(i, parameter);
4 }

  获取到的Statement对象详情如下,已生成可执行的SQL语句。 

  截图.png

3.3、执行SQL查询

  获取到PreparedStatement对象,执行SQL,处理结果集,PreparedStatementHandler#query() 核心代码:

1 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
2   PreparedStatement ps = (PreparedStatement) statement;
3   //  JDBC的执行SQL查询
4   ps.execute();
5   // 处理结果集
6   return resultSetHandler.handleResultSets(ps);
7 }

  DefaultResultSetHandler#handleResultSets() 核心代码:

 1 public List<Object> handleResultSets(Statement stmt) throws SQLException {
 2   ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
 3 
 4   // 该集合用于保存映射结果得到的结果对象
 5   final List<Object> multipleResults = new ArrayList<>();
 6 
 7   int resultSetCount = 0;
 8   // 创建ResultSetWrapper对象,封装resultSet
 9   ResultSetWrapper rsw = getFirstResultSet(stmt);
10 
11   // 获取MappedStatement.resultMaps集合
12   List<ResultMap> resultMaps = mappedStatement.getResultMaps();
13   int resultMapCount = resultMaps.size();
14 
15   // 遍历resultMaps集合
16   while (rsw != null && resultMapCount > resultSetCount) {
17     // 获取该结果集对应的ResultMap对象
18     ResultMap resultMap = resultMaps.get(resultSetCount);
19     // 根据ResultMap中定义的映射规则对ResultSet进行映射,并将映射的结果对象添加到multipleResult集合中保存
20     handleResultSet(rsw, resultMap, multipleResults, null);
21     // 获取下一个结果集
22     rsw = getNextResultSet(stmt);
23     // 清空nestedResultObjects集合
24     cleanUpAfterHandlingResultSet();
25     // 递增resultSetCount
26     resultSetCount++;
27   }
28   // ...
29 
30   return collapseSingleResultList(multipleResults);
31 }

1、创建ResultSetWrapper对象封装ResultSet

  ResultSetWrapper持有ResultSet,类型处理器注册器,包含表字段、表字段类型及表字段对应的Java类型,ResultSetWrapper详情如下:

  截图.png

2、获取MappedStatement.resultMaps集合

  MappedStatement.resultMaps是在解析Mapper.xml的select|insert|update|delete标签,创建MappedStatement.Builder对象时,完成的初始化操作。

  初始化resultMaps的链路:XMLMapperBuilder#configurationElement() -> XMLMapperBuilder#buildStatementFromContext() -> XMLStatementBuilder#parseStatementNode() -> MapperBuilderAssistant#addMappedStatement()

  resultMaps持有结果映射的类型type,statementId -> 目标执行方法全限定名,resultMaps示例详情:

  截图.png

3、结果集映射成结果对象集合

  遍历resultMaps集合,逐个处理resultMap,DefaultResultSetHandler#handleResultSet() 核心伪代码:

 1 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
 2   try {
 3       // ...
 4       // 用户未指定处理映射结果对象的ResultHandler对象,则使用DefaultResultHandler作为默认的ResultHandler对象
 5       if (resultHandler == null) {
 6         // 默认的结果处理器
 7         DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
 8         // 对ResultSet进行映射,并将映射得到的结果对象添加到DefaultResultHandler对象中暂存
 9         handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
10         // 将DefaultResultHandler中保存的结果对象添加到multipleResults集合中
11         multipleResults.add(defaultResultHandler.getResultList());
12         
13       // 使用用户指定的ResultHandler对象处理结果对象
14       } else {
15         handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
16       }
17     }
18   } finally {
19     // 调用ResultSet.close方法关闭结果集
20     closeResultSet(rsw.getResultSet());
21   }
22 }

  ResultSet结果集的处理,可以自定义resultHandler结果集处理器;若未指定resultHandler,使用默认的结果集处理器DefaultResultHandler。默认结果集处理器DefaultResultHandler通过ObjectFactory对象实例化List属性,DefaultResultHandler详情:

  截图.png

  对结果集进行映射,会将映射的对象设置在defaultResultHandler对象的List属性中。

handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
1、创建结果映射对象并完成对象属性填充,将填充完成的对象存储在DefaultResultSetHandler的List属性中

  最终会执行到 DefaultResultSetHandler的handleRowValuesForSimpleResultMap()方法

 1 private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
 2     throws SQLException {
 3   // 默认上下文对象
 4   DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
 5   ResultSet resultSet = rsw.getResultSet();
 6   // 根据RowBounds中的offset定位到指定的记录
 7   skipRows(resultSet, rowBounds);
 8   // 检测已经处理的行数是否已经达到上限(RowBounds,limit)以及ResultSet中是否还有可处理的记录
 9   while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
10     // 根据该行记录以及ResultMap.discriminator,决定映射使用的ResultMap
11     ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
12     // 根据最终确定的ResultMap对ResultSet中的该行记录进行映射,得到映射后的结果对象
13     Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
14     // 将映射创建的结果对象添加到ResultHandler.resultList中保存
15     storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
16   }
17 }

  将结果集映射成结果对象 DefaultResultSetHandler#getRowValue() 核心代码:

 1 private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
 2   // 实例化ResultLoaderMap(延迟加载器)
 3   final ResultLoaderMap lazyLoader = new ResultLoaderMap();
 4   // 创建该行记录映射之后得到的结果对象,该结果对象的类型由ResultMap节点的type属性指定
 5   Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
 6   if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
 7     // 创建上述结果对象相应的MetaObject对象
 8     final MetaObject metaObject = configuration.newMetaObject(rowValue);
 9     // 成功映射任意属性,则foundValues为true,否则foundValues为false
10     boolean foundValues = this.useConstructorMappings;
11     // 检测是否需要进行自动映射
12     if (shouldApplyAutomaticMappings(resultMap, false)) {
13       // 自动映射ResultMap中未明确指定的列
14       foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
15     }
16     // 映射ResultMap中明确指定需要映射的列
17     foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
18     foundValues = lazyLoader.size() > 0 || foundValues;
19     // 如果没有成功映射任何属性,则根据mybatis-config.xml中的returnInstanceForEmptyRow配置决定返回空的结果对象还是返回null
20     rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
21   }
22   return rowValue;
23 }
1、实例化空的映射结果对象

  createResultObject() 根据resultMap中的type实例化对象,rowValue详情如下

截图.png
2、映射结果属性填充

  DefaultResultSetHandler#applyAutomaticMappings() 核心代码

 1 // 自动映射
 2 private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
 3   // 获取ResultSet中存在,但ResultMap中没有明确映射的列所对应的UnMappedColumnAutoMapping集合,如果ResultMap中设置的resultType为HashMap的话,则全部的列都会在这里获取到
 4   List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
 5   boolean foundValues = false;
 6   if (!autoMapping.isEmpty()) {
 7     // 遍历autoMapping集合
 8     for (UnMappedColumnAutoMapping mapping : autoMapping) {
 9       // 使用TypeHandler获取自动映射的列值
10       final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
11       if (value != null) {
12         foundValues = true;
13       }
14       if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
15         // 将自动映射的属性值设置到结果对象中
16         metaObject.setValue(mapping.property, value);
17       }
18     }
19   }
20   return foundValues;
21 }

  获取autoMapping,即获取数据字段与Java实体对象的映射关系及类型处理器,autoMapping详情如下:

   截图.png

  遍历autoMapping集合,通过类型处理器获取自动映射的列值,根据映射结果对象的元数据对象metaObject,完成对象属性的设置,本质上是通过反射完成赋值操作的。

3、映射结果对象信息存储

  映射结果对象信息最后会添加进DefaultResultHandler的List属性中进行存储,遍历下一个ResultSet结果集继续上述流程处理。

  DefaultResultSetHandler#storeObject() 核心伪代码

1 private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
2     // 普通映射,将结果对象保存到ResultHandler中
3     callResultHandler(resultHandler, resultContext, rowValue);
4 }

  DefaultResultSetHandler#callResultHandler() 核心伪代码:

1 private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
2   //将结果对象保存到 DefaultResultContext.resultObject字段中
3   resultContext.nextResultObject(rowValue);
4   // 将结果对象添加到ResultHandler.resultList中保存
5   ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
6 }

  DefaultResultHandler#handleResult() 核心代码:

public void handleResult(ResultContext<? extends Object> context) {
  list.add(context.getResultObject());
}
2、将暂存在DefaultResultSetHandler#List中映射结果对象集合赋值给multipleResults集合
3、关闭ResultSet

3.4、返回映射对象结果集合

3.5、关闭Statement

  至此,完成了SQL查询的分析。