阅读 MyBatis 源码(SQL 执行过程)

上一篇文章介绍了 JDBC 实现 SQL 查询的原理 ,本文通过一个简单的 MyBatis 查询示例,探究 MyBatis 执行 SQL 查询的代码流程。

本文基于 MyBatis 3.5.7。
1. 使用示例 工程结构:
阅读 MyBatis 源码(SQL 执行过程)
文章图片

简单查询例子:
private static SqlSessionFactory sqlSessionFactory; @BeforeClass public static void init() { try { Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } catch (IOException e) { e.printStackTrace(); } }@Test public void selectAll() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { List students = sqlSession.selectList("selectAll"); for (int i = 0; i < students.size(); i++) { System.out.println(students.get(i)); } } catch (Exception e) { e.printStackTrace(); } }

2. 源码分析 2.1 解析配置文件 2.1.1 解析 mybatis-config.xml
MyBatis XML 配置文件例子如下,更多说明见官方文档-XML配置
【阅读 MyBatis 源码(SQL 执行过程)】

通过 SqlSessionFactoryBuilder 来解析 mybatis-config.xml 文件:
Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

这里会把 mybatis-config.xml 配置文件解析成 org.apache.ibatis.session.Configuration 对象,这是一个重要的配置类。
org.apache.ibatis.session.SqlSessionFactoryBuilder#build
org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }

解析 mybatis-config.xml 中的每一个节点。
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) { try { // issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); // 解析插件配置 objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); // 解析环境配置:数据源、事务管理 databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); // 解析映射文件 } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }

解析得到 Configuration 对象之后,可以用它来构造 SqlSessionFactory 对象。
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }

2.1.2 解析 SQL 映射文件
SQL 映射文件 com\sumkor\mapper\StudentMapper.xml 示例如下,更多说明见官方文档-XML映射
id, name, phone, email, sex, locked, gmt_created, gmt_modifiedselect from student

在解析 mybatis-config.xml 文件的时候,解析 mappers 标签,找到对应的 SQL 映射文件。
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
org.apache.ibatis.session.Configuration#addMappers
org.apache.ibatis.binding.MapperRegistry#addMapper
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.isEmpty()) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }

SQL 映射文件中一个 节点,会被解析为 MappedStatement 对象。
org.apache.ibatis.mapping.MappedStatement
// 该对象表示 Mapper.xml 中的一条 SQL 信息,相关标签见 org\apache\ibatis\builder\xml\mybatis-3-mapper.dtd public final class MappedStatement {private String resource; private Configuration configuration; private String id; private Integer fetchSize; // 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。默认值为未设置(unset)(依赖驱动)。 private Integer timeout; // 驱动程序等待数据库返回请求结果的秒数,超时将会抛出异常 private StatementType statementType; // 参数可选值为 STATEMENT、PREPARED 或 CALLABLE,这会让 MyBatis 分别使用 Statement、PreparedStatement 或 CallableStatement 与数据库交互,默认值为 PREPARED private ResultSetType resultSetType; // 参数可选值为 FORWARD_ONLY、SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE,用于设置从结果集读取数据时,读指针能否上下移动。例如,只需要顺序读取,可设置为 FORWARD_ONLY,便于释放已读内容所占的内存 private SqlSource sqlSource; // sql 语句 private Cache cache; // 二级缓存,若无配置则为空 private ParameterMap parameterMap; private List resultMaps; private boolean flushCacheRequired; // 对应 xml 中的 flushCache 属性。将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。 private boolean useCache; // 对应 xml 中的 useCache 属性。将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。 private boolean resultOrdered; // 这个设置仅针对嵌套结果 select 语句。默认值:false。 private SqlCommandType sqlCommandType; // sql 语句的类型,如 select、update、delete、insert private KeyGenerator keyGenerator; private String[] keyProperties; private String[] keyColumns; private boolean hasNestedResultMaps; private String databaseId; private Log statementLog; private LanguageDriver lang; private String[] resultSets;

生成 MappedStatement 对象之后,会将其注册到 Configuration 对象之中:
org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement
org.apache.ibatis.session.Configuration#addMappedStatement
protected final Map mappedStatements = new StrictMap("Mapped Statements collection") .conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and " + targetValue.getResource()); public void addMappedStatement(MappedStatement ms) { mappedStatements.put(ms.getId(), ms); }

Configuration 对象中的 mappedStatements 属性是一个 StrictMap 类型,是 MyBatis 的一个内部类。
可以看到,这里以 MappedStatement#id 进行注册,会同时注册一个短名称(eg:selectAll)和长名称(eg:com.sumkor.mapper.StudentMapper.selectAll)。
org.apache.ibatis.session.Configuration.StrictMap#put
@Override @SuppressWarnings("unchecked") public V put(String key, V value) { if (containsKey(key)) { throw new IllegalArgumentException(name + " already contains value for " + key + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value))); } if (key.contains(".")) { final String shortKey = getShortName(key); if (super.get(shortKey) == null) { super.put(shortKey, value); // 以短名称进行注册 } else { super.put(shortKey, (V) new Ambiguity(shortKey)); // 短名称注册出现冲突 } } return super.put(key, value); // 以全名称注册 }

2.2 开启会话
SqlSession sqlSession = sqlSessionFactory.openSession()

对于 SqlSession 的官方说明:
SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
在 Web 应用中,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。
构建 SqlSession 实例,代码如下。默认 autoCommit 为 false。
注意,每一个 SqlSession 会话都有独自的 Executor 和 Transaction 实例。
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // 通过事务工厂,实例化 Transaction 事务对象 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 实例化 Executor 执行器对象,通过它来执行 SQL,支持插件扩展 final Executor executor = configuration.newExecutor(tx, execType); // 构建 SqlSession return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session.Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }

默认使用 SimpleExecutor 作为执行器,作为 MyBatis 的调度中心。
org.apache.ibatis.session.Configuration#newExecutor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; // 该类型的执行器会批量执行所有更新语句,如果 SELECT 在多个更新中间执行,将在必要时将多条更新语句分隔开来,以方便理解。 if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } // 该类型的执行器会复用预处理语句。 else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } // 该类型的执行器没有特别的行为。它为每个语句的执行创建一个新的预处理语句。 else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } // 使用插件来包装 executor executor = (Executor) interceptorChain.pluginAll(executor); return executor; }

2.3 执行 SQL
List students = sqlSession.selectList("selectAll");

  • 全限定名(比如 “com.sumkor.mapper.StudentMapper.selectAll)将被直接用于查找及使用。
  • 短名称(比如 “selectAll”)如果全局唯一也可以作为一个单独的引用。如果不唯一,有两个或两个以上的相同名称(比如 “com.foo.selectAll” 和 “com.bar.selectAll”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。
这里通过字符串 selectAll 从 Configuration 对象之中,查找到对应的 MappedStatement 对象。
org.apache.ibatis.session.defaults.DefaultSqlSession#selectList
private List selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { try { MappedStatement ms = configuration.getMappedStatement(statement); // 根据 SQL id 从配置类中获取 SQL 解析后的对象 return executor.query(ms, wrapCollection(parameter), rowBounds, handler); // 执行 SQL } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database.Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }

2.3.1 Executor#query
查询数据库之前,会先查询 MyBatis 缓存。这里只关注数据库查询的部分:
org.apache.ibatis.executor.CachingExecutor#query
org.apache.ibatis.executor.BaseExecutor#query
org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
org.apache.ibatis.executor.SimpleExecutor#doQuery
@Override public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 创建 StatementHandler,支持插件扩展 stmt = prepareStatement(handler, ms.getStatementLog()); // 获取数据库连接对象 Connection,以构造 Statement 对象 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }

Executor 对象的主要功能是创建并使用 StatementHandler 访问数据库,并将查询结果存入缓存中(如果配置了缓存的话)。
2.3.2 Executor#prepareStatement
Executor 在创建得到 StatementHandler 对象之后,利用 StatementHandler#prepare 来构建 Statement 对象。
org.apache.ibatis.executor.SimpleExecutor#prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); // 获取数据库连接对象 stmt = handler.prepare(connection, transaction.getTimeout()); // 使用 Connection 对象构造 Statement 对象 handler.parameterize(stmt); // 其中会使用 ParameterHandler 设置参数,支持插件扩展 return stmt; }

org.apache.ibatis.executor.statement.BaseStatementHandler#prepare
org.apache.ibatis.executor.statement.BaseStatementHandler#instantiateStatement
org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement
protected Statement instantiateStatement(Connection connection) throws SQLException { // ... String sql = boundSql.getSql(); return connection.prepareStatement(sql); }

到了 JDBC 层面,通过 Connection 来创建 Statement 对象,最后获得 ClientPreparedStatement 实例。
java.sql.Connection#prepareStatement(java.lang.String, int, int)
com.mysql.cj.jdbc.ConnectionImpl#prepareStatement
com.mysql.cj.jdbc.ConnectionImpl#clientPrepareStatement(java.lang.String, int, int, boolean)
com.mysql.cj.jdbc.ClientPreparedStatement#getInstance(com.mysql.cj.jdbc.JdbcConnection, java.lang.String, java.lang.String)
protected static ClientPreparedStatement getInstance(JdbcConnection conn, String sql, String db) throws SQLException { return new ClientPreparedStatement(conn, sql, db); }

2.3.3 StatementHandler#query
获取得到 Statement 对象之后,利用 StatementHandler#query 方法来执行 SQL 语句。
org.apache.ibatis.executor.SimpleExecutor#doQuery
org.apache.ibatis.executor.statement.RoutingStatementHandler#query
org.apache.ibatis.executor.statement.PreparedStatementHandler#query
@Override public List query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps); }

到了 JDBC 层面,交由 MySQL 驱动中的 PreparedStatement 对象来执行 SQL。
org.apache.ibatis.logging.jdbc.PreparedStatementLogger#invoke
com.mysql.cj.jdbc.ClientPreparedStatement#execute
2.4 结果映射 在 StatementHandler#query 中通过 PreparedStatement 执行完 SQL 之后,向 MySQL 服务器读取响应。
org.apache.ibatis.executor.SimpleExecutor#doQuery
org.apache.ibatis.executor.statement.RoutingStatementHandler#query
org.apache.ibatis.executor.statement.PreparedStatementHandler#query
@Override public List query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps); }

通过 JDBC 从数据库读取到的是二进制数据,封装在 ResultSet 对象中。
需要将 ResultSet 中的数据,转换成配置在 SQL 映射文件的 ResultMap 对象。
本例中 ResultMap 配置如下,更多配置说明见官方文档-XML映射

代码流程:
  1. 从数据库读取的响应数据 ResultSet 对象。
  2. 获取配置在 SQL 映射文件的 ResultMap 对象,生成对应的 Java 实体对象。
  3. 利用 ResultSet 对象中的数据,对 Java 实体对象的属性进行赋值。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
@Override public List handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List multipleResults = new ArrayList<>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); // 从数据库读取的响应数据 ResultSet 对象List resultMaps = mappedStatement.getResultMaps(); // 配置在 SQL 映射文件的 ResultMap 对象 int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); // 生成 Java 实体对象并进行属性赋值,存储在 multipleResults rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; }String[] resultSets = mappedStatement.getResultSets(); // (在使用存储过程的情况下)如果配置了多结果集,则进一步处理 if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } }return collapseSingleResultList(multipleResults); }
值得注意的是,并不是所有在 ResultSet 中的二进制数据,都需要转换成 Java 实体对象。
  1. 使用 RowBounds 的情况下,说明用到了 MyBatis 提供的通用分页功能。只需要取 ResultSet 的 offset 到 limit 范围的数据进行对象映射。
  2. 使用 Discriminator 的情况下,需要根据鉴别器的条件,进行对象映射。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSet
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValues
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForSimpleResultMap
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext resultContext = new DefaultResultContext<>(); // 在查询数据库时,如果没有 limit 语句,则 ResultSet 中会包含所有满足条件的数据 ResultSet resultSet = rsw.getResultSet(); // 把 offset 之前的数据都 skip 掉 skipRows(resultSet, rowBounds); // 判断数据是否超过了 limit while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { // 如果配置了鉴别器 Discriminator,则对查询的结果进行分支处理 ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null); // 将 ResultSet 中的一行数据转换为 ResultMap 的一个对象并赋值 Object rowValue = https://www.it610.com/article/getRowValue(rsw, discriminatedResultMap, null); // 将结果存储到 ResultHandler 之中 storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } }
关于 ResultMap 中的鉴别器 Discriminator 和多结果集的说明,见官方文档-XML映射
2.4.1 数据对象映射
  1. 根据 ResultMap 获取 Java 实体对象。
  2. 为 Java 实体对象的属性赋值。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); Object rowValue = https://www.it610.com/article/createResultObject(rsw, resultMap, lazyLoader, columnPrefix); // 获取 Java 实体类的实例,属性均为空 if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(rowValue); // 反射工具类 MetaObject boolean foundValues = this.useConstructorMappings; if (shouldApplyAutomaticMappings(resultMap, false)) { foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; // 利用 MetaObject 为 Java 实体的属性赋值 foundValues = lazyLoader.size()> 0 || foundValues; rowValue = https://www.it610.com/article/foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } return rowValue; }

2.4.2 根据 ResultMap 获取 Java 实体对象
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List> constructorArgTypes, List constructorArgs, String columnPrefix) throws SQLException { final Class resultType = resultMap.getType(); // Java 实体类型 final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory); // 反射工具类 MetaClass final List constructorMappings = resultMap.getConstructorResultMappings(); if (hasTypeHandlerForResultObject(rsw, resultType)) { return createPrimitiveResultObject(rsw, resultMap, columnPrefix); } else if (!constructorMappings.isEmpty()) { return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix); } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) { return objectFactory.create(resultType); // 创建 Java 实体类的实例 } else if (shouldApplyAutomaticMappings(resultMap, false)) { return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs); } throw new ExecutorException("Do not know how to create an instance of " + resultType); }
2.4.3 为 Java 实体对象的属性赋值
从 ResultSet 中读取每一个字段的值,再赋值给 Java 实体的属性。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#applyPropertyMappings
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { final List mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix); boolean foundValues = false; final List propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { // 遍历所有字段 String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); // 表字段名,例如:gmt_created if (propertyMapping.getNestedResultMapId() != null) { // the user added a column attribute to a nested result map, ignore it column = null; } if (propertyMapping.isCompositeResult() || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) || propertyMapping.getResultSet() != null) { Object value = https://www.it610.com/article/getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix); // 获取字段的值 // issue #541 make property optional final String property = propertyMapping.getProperty(); // 实体属性名,例如:gmtCreated if (property == null) { continue; } else if (value == DEFERRED) { foundValues = true; continue; } if (value != null) { foundValues = true; } if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) { // gcode issue #377, call setter on nulls (value is not'found') metaObject.setValue(property, value); // 为 Java 实体对象的字段赋值 } } } return foundValues; }

至于如何从 ResultSet 中读取一个字段的值,这里是用到了 TypeHandler,做到 java 数据类型和 jdbc 数据类型之间的映射和转换。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getPropertyMappingValue
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { if (propertyMapping.getNestedQueryId() != null) { return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix); } else if (propertyMapping.getResultSet() != null) { addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK? return DEFERRED; } else { final TypeHandler typeHandler = propertyMapping.getTypeHandler(); // 获取字段映射所需的 TypeHandler final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); // 获取数据库表的字段名 return typeHandler.getResult(rs, column); // 进行 java 数据类型和 jdbc 数据类型之间的映射和转换 } }

以 IntegerTypeHandler 为例,从 ResultSet 之中读取字段名 columnName 对应的数据,该字段的数据类型为 int。
org.apache.ibatis.type.IntegerTypeHandler#getNullableResult
@Override public Integer getNullableResult(ResultSet rs, String columnName) throws SQLException { int result = rs.getInt(columnName); return result == 0 && rs.wasNull() ? null : result; }

3. 总结 MyBatis 执行 SQL 的流程:
  1. 加载配置:将 MyBatis XML 配置文件解析为 Configuration 对象,其中,会将 SQL 映射文件中的 SQL 语句解析为 MappedStatement 对象,存储在 Configuration 对象中。
  2. SQL 解析:通过 SQLSession 接收到请求时,根据传入的 SQL id 找到对应的 MappedStatement 对象,该对象中包含解析后的 SQL 语句。
  3. SQL 执行:由 Executor 进行调度,利用 StatementHandler 向数据库发起查询,底层是使用 JDBC 的 Statement#execute 方法来查询数据库,得到查询结果为 ResultSet 对象。
  4. 结果映射:按照映射的配置对查询结果进行转换,可以转换成 HashMap、JavaBean 或者基本数据类型,并将最终结果返回。
作者:Sumkor
链接:https://segmentfault.com/a/11...

    推荐阅读