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

本文基于 MyBatis 3.5.7。

1. 应用示例

工程构造:

简略查问例子:

private static SqlSessionFactory sqlSessionFactory;@BeforeClasspublic static void init() {    try {        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);    } catch (IOException e) {        e.printStackTrace();    }}@Testpublic void selectAll() {    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {        List<Student> 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配置

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration>    <!-- MyBatis XML 配置阐明 https://mybatis.org/mybatis-3/zh/configuration.html -->    <!-- 引入其余配置文件 -->    <properties resource="jdbc.properties"/>    <settings>        <!-- 设置一级缓存的范畴 -->        <setting name="localCacheScope" value="SESSION"/>        <!-- 开启缓存 -->        <setting name="cacheEnabled" value="true"/>        <!-- 开启驼峰式命名,数据库的列名可能映射到去除下划线驼峰命名后的字段名-->        <setting name="mapUnderscoreToCamelCase" value="true"/>        <!-- 指定 MyBatis 所用日志的具体实现,未指定时将主动查找 -->        <setting name="logImpl" value="LOG4J"/>    </settings>    <!-- 类型别名可为 Java 类型设置一个缩写名字。它仅用于 XML 配置,意在升高冗余的全限定类名书写 -->    <typeAliases>        <!-- 指定一个包名,MyBatis 会在包名上面搜寻须要的 Java Bean。在没有注解的状况下,会应用 Bean 的首字母小写的非限定类名来作为它的别名 -->        <package name="com.sumkor.entity"/>    </typeAliases>    <plugins>        <plugin interceptor="com.sumkor.plugin.StatementInterceptor"/>        <plugin interceptor="com.sumkor.plugin.PageInterceptor"/>    </plugins>    <environments default="development">        <environment id="development">            <transactionManager type="JDBC">                <property name="" value=""/>            </transactionManager>            <!-- POOLED 这种数据源的实现利用“池”的概念将 JDBC 连贯对象组织起来,防止了创立新的连贯实例时所必须的初始化和认证工夫。 -->            <dataSource type="POOLED">                <property name="driver" value="${jdbc.driver}"/>                <property name="url" value="${jdbc.url}"/>                <property name="username" value="${jdbc.username}"/>                <property name="password" value="${jdbc.password}"/>            </dataSource>        </environment>    </environments>    <mappers>        <!-- 应用 package 标签,须要把 xml 映射文件和 Mapper 接口文件放在同一个目录,而且必须同名 -->        <package name="com.sumkor.mapper"/>        <!-- <mapper resource="com/sumkor/mapper/StudentMapper.xml"/> -->    </mappers></configuration>

通过 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映射

<mapper namespace="com.sumkor.mapper.StudentMapper">    <!-- 要留神 MyBatis 不会通过检测数据库元信息来决定应用哪种类型,所以必须在参数和后果映射中指明字段的 jdbcType 类型,以使其可能绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才分明数据类型 -->    <resultMap id="BaseResultMap" type="com.sumkor.entity.Student">        <id column="id" jdbcType="INTEGER" property="id"/>        <result column="name" jdbcType="VARCHAR" property="name"/>        <result column="phone" jdbcType="VARCHAR" property="phone"/>        <result column="email" jdbcType="VARCHAR" property="email"/>        <result column="sex" jdbcType="TINYINT" property="sex"/>        <result column="locked" jdbcType="TINYINT" property="locked"/>        <result column="gmt_created" jdbcType="TIMESTAMP" property="gmtCreated"/>        <result column="gmt_modified" jdbcType="TIMESTAMP" property="gmtModified"/>        <!-- 反对的 jdbcType 见 org.apache.ibatis.type.JdbcType 或者 https://mybatis.org/mybatis-3/zh/sqlmap-xml.html -->    </resultMap>    <sql id="base_column_list">        id, name, phone, email, sex, locked, gmt_created, gmt_modified    </sql>    <select id="selectAll" resultMap="BaseResultMap">        select        <include refid="base_column_list"/>        from student    </select></mapper>    

在解析 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 映射文件中一个 <select|update|delete|insert> 节点,会被解析为 MappedStatement 对象。

org.apache.ibatis.mapping.MappedStatement

// 该对象示意 Mapper.xml 中的一条 SQL 信息,相干标签见 org\apache\ibatis\builder\xml\mybatis-3-mapper.dtdpublic 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<ResultMap> 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<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("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<Student> 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 <E> List<E> 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 <E> List<E> 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 <E> List<E> 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 <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {    PreparedStatement ps = (PreparedStatement) statement;    ps.execute();    return resultSetHandler.handleResultSets(ps);  }

通过 JDBC 从数据库读取到的是二进制数据,封装在 ResultSet 对象中。
须要将 ResultSet 中的数据,转换成配置在 SQL 映射文件的 ResultMap 对象。

本例中 ResultMap 配置如下,更多配置阐明见官网文档-XML映射

<mapper namespace="com.sumkor.mapper.StudentMapper">    <!-- 要留神 MyBatis 不会通过检测数据库元信息来决定应用哪种类型,所以必须在参数和后果映射中指明字段的 jdbcType 类型,以使其可能绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才分明数据类型 -->    <resultMap id="BaseResultMap" type="com.sumkor.entity.Student">        <id column="id" jdbcType="INTEGER" property="id"/>        <result column="name" jdbcType="VARCHAR" property="name"/>        <result column="phone" jdbcType="VARCHAR" property="phone"/>        <result column="email" jdbcType="VARCHAR" property="email"/>        <result column="sex" jdbcType="TINYINT" property="sex"/>        <result column="locked" jdbcType="TINYINT" property="locked"/>        <result column="gmt_created" jdbcType="TIMESTAMP" property="gmtCreated"/>        <result column="gmt_modified" jdbcType="TIMESTAMP" property="gmtModified"/>        <!-- 反对的 jdbcType 见 org.apache.ibatis.type.JdbcType 或者 https://mybatis.org/mybatis-3/zh/sqlmap-xml.html -->    </resultMap></mapper>    

代码流程:

  1. 从数据库读取的响应数据 ResultSet 对象。
  2. 获取配置在 SQL 映射文件的 ResultMap 对象,生成对应的 Java 实体对象。
  3. 利用 ResultSet 对象中的数据,对 Java 实体对象的属性进行赋值。

org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets

  @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;    ResultSetWrapper rsw = getFirstResultSet(stmt); // 从数据库读取的响应数据 ResultSet 对象    List<ResultMap> 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<Object> 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 = 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 = 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 = 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<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)      throws SQLException {    final Class<?> resultType = resultMap.getType(); // Java 实体类型    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory); // 反射工具类 MetaClass    final List<ResultMapping> 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<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);    boolean foundValues = false;    final List<ResultMapping> 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 = 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 对象。
  3. SQL 执行:由 Executor 进行调度,向缓存或数据库发动查问,失去查问后果。
  4. 后果映射:依照映射的配置对查问后果进行转换,能够转换成 HashMap、JavaBean 或者根本数据类型,并将最终后果返回。

作者:Sumkor
链接:https://segmentfault.com/a/11...