mybatis如何解析xml和加载Configuration

一、加载xml的入口

mybatis配置xml路径的类,一般都在SqlSessionFactoryBean。怎么加载它呢?有两种方式:

  1. xml形式:
<bean id="xxxSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
        p:dataSource-ref="xxxDataSource" p:mapperLocations="classpath*:mapper/*.xml">
        <property name="configuration">
            <bean class="org.apache.ibatis.session.Configuration">
                <property name="mapUnderscoreToCamelCase" value="true" />
            </bean>
        </property>
    </bean>
  1. @Configuration注解形式
    @Bean(name = "xxxSqlSessionFactory")
    public SqlSessionFactory xxxSqlSessionFactory(@Qualifier("xxxDataSource")  DataSource dataSource){
        SqlSessionFactory sqlSessionFactory = null;
        try {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean() ;
            sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources("classpath*:mapper/crt/*.xml"));
            sqlSessionFactoryBean.setDataSource(dataSource);
            sqlSessionFactory = sqlSessionFactoryBean.getObject();
            sqlSessionFactory.getConfiguration().setMapUnderscoreToCamelCase(true);
        } catch (Exception e) {
            LOGGER.error(e.getMessage(),e);
            throw new CouponBizException(CouponBizCodeEnum.SYSTEM_ERROR) ;
        }

        return sqlSessionFactory ;
    }

不管哪种方式,最终都会执行到sqlSessionFactoryBean.getObject()方法,得到一个SqlSessionFactory接口的实现类,具体是哪个实现类,下面会讲到。好了,入口知道了,我们就开始继续深入吧~

二、加载Configuration

应用在启动的时候,Spring容器会触发sqlSessionFactoryBean.getObject()方法得到单例对象,以便放到容器中。看下getObject方法:

 @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }
    return this.sqlSessionFactory;
  }

第一次必然会调用afterPropertiesSet()。继续深入看下源码:

@Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

sqlSessionFactory接口的实现类,就在buildSqlSessionFactory()里面。继续深入:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;

    //说明了mapper接口的配置,默认用xml来实现
    XMLConfigBuilder xmlConfigBuilder = null;
   //上面的xml配置,我们已经传入configuration了,这里必然会进来
    if (this.configuration != null) {
      configuration = this.configuration;
    //property字段,我们并没有配置多余其他字段,所以这里都不会进来
      if (configuration.getVariables() == null) {
        configuration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        configuration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      if (this.configurationProperties != null) {
        configuration.setVariables(this.configurationProperties);
      }
    }

    //xml的配置未配置该<property />标签,无需进来
    if (this.objectFactory != null) {
      configuration.setObjectFactory(this.objectFactory);
    }

 //xml的配置未配置该<property />标签,无需进来
    if (this.objectWrapperFactory != null) {
      configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }

 //xml的配置未配置该<property />标签,无需进来
    if (this.vfs != null) {
      configuration.setVfsImpl(this.vfs);
    }

 //xml的配置未配置该<property />标签,无需进来
    if (hasLength(this.typeAliasesPackage)) {
      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeAliasPackageArray) {
        configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
        }
      }
    }

 //xml的配置未配置该<property />标签,无需进来
    if (!isEmpty(this.typeAliases)) {
      for (Class<?> typeAlias : this.typeAliases) {
        configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type alias: '" + typeAlias + "'");
        }
      }
    }

 //xml的配置未配置该<property />标签,无需进来
    if (!isEmpty(this.plugins)) {
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered plugin: '" + plugin + "'");
        }
      }
    }

 //xml的配置未配置该<property />标签,无需进来
    if (hasLength(this.typeHandlersPackage)) {
      String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeHandlersPackageArray) {
        configuration.getTypeHandlerRegistry().register(packageToScan);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
        }
      }
    }

 //xml的配置未配置该<property />标签,无需进来
    if (!isEmpty(this.typeHandlers)) {
      for (TypeHandler<?> typeHandler : this.typeHandlers) {
        configuration.getTypeHandlerRegistry().register(typeHandler);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type handler: '" + typeHandler + "'");
        }
      }
    }

 //xml的配置未配置该<property />标签,无需进来
    if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
      try {
        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

 //xml的配置未配置该<property />标签,无需进来
    if (this.cache != null) {
      configuration.addCache(this.cache);
    }

//xmlConfigBuilder尚未初始化,无需进来
    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
        }
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }

     //xml的配置未配置该<property />标签,所以必为null,需要进来
    if (this.transactionFactory == null) {
      //默认的transactionFactory是SpringManagedTransactionFactory
      this.transactionFactory = new SpringManagedTransactionFactory();
    }
    //创建Environment,其实就是为了封装dataSource和transactionFactory
    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

    //xml的配置了mapperLocations,所以不为null,需要进来
    if (!isEmpty(this.mapperLocations)) {
      //遍历每个mapper文件
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
        //为每个xml文件,初始化一个xmlMapperBuilder。它含有configuration,目的就是容纳下面parse xml得到的结果
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
        //开始解析xml文件,并把解析出来的所有标签,放到configuration对应的字段上
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }

    return this.sqlSessionFactoryBuilder.build(configuration);
  }

有点长。。。
看完里面的注释,也就都明白了。加载configuration的核心就在xmlMapperBuilder.parse()方法里面。

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

然后进入configurationElement方法:

private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        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);
    }
  }

可以看到先解析<mapper> .... </mapper>标签里面的东东,然后递归继续解析mapper里面的<resultMap>....</resultMap>,以及<sql> ....</sql>。我们来看一下,mapper的xml标签格式好了。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xxx.xxx.xxxMapper">

    <resultMap id="xxxMap" type="com.xxx.xxx">
        <id column="id" property="id"/>
        <result column="uid" property="uid" />
        <result column="created_time" property="createdTime" />
        <result column="updated_time" property="updatedTime" />     
      ....
    </resultMap>

    <sql id="Base_Column_List">
        id, uid....
    </sql>

    <insert id="insert" parameterType="com.xxx.xxx">
        insert into xxx values()
    </insert>

    <select id="getById" parameterType="java.util.Map" resultType="com.xxx.xxx">
        select <include refid="Base_Column_List" />  from xxx   where id=#{id}
    </select>

    <update id="updateStatusById" parameterType="java.util.Map">
          update xxxx
          set status= #{targetStatus, jdbcType=NUMERIC}
          where id = #{id} and status = #{sourceStatus}
    </update>
</mapper>

可以看到解析的入口就在<mapper>标签,而它恰好就是mapper xml文件的格式。然后依次按照规范解析其余标签,以及标签里面的属性信息,放到Configuration对应的字段。
下面我们以解析<select>标签为例,解析源码:

public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

获取<select>标签上面的属性,很多属性是不是很陌生。但是最常见的几个属性,我们一定知道。比如:id, parameterMap, resultMap。其实这些属性在mybatis的dtd描述文件里面就有的,不信我们点击xml的<select>标签,是可以点击进去的。看到如下:

<!ELEMENT select (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST select
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
resultMap CDATA #IMPLIED
resultType CDATA #IMPLIED
resultSetType (FORWARD_ONLY | SCROLL_INSENSITIVE | SCROLL_SENSITIVE) #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
fetchSize CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true|false) #IMPLIED
useCache (true|false) #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
resultOrdered (true|false) #IMPLIED
resultSets CDATA #IMPLIED 
>

id, parameterMap, resultMap等等每一个属性都有定义好,只不过解析的时候拿出来就好了。最终所有字段解析完成后,会调用如下:

builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

该方法会生成MappedStatement,它会接收<select>标签所有属性,包括:真正的sql语句。但是我们的sql语句是动态的,也就是说有条件的,只有在真正执行的才能确定sql语句。那么xml的静态的sql语句是怎么保存的呢?答案就在sqlSource字段。debug查看,如下:


image.png

内容被切割为不同类型的对象了,比如:StaticTextSqlNode对象存放固定sql语句,如where前面的语句,它肯定是固定不变的,无需动态生成。而IfSqlNode它有test字段String类型,又有contents字段,而contents又是一个StaticTextSqlNode属于静态不变的语句。
可以看出mybatis的动态sql,会在初始化的时候生成sqlSource这种模板,后面再运行的时候,会根据sql请求参数,匹配这个sqlSource,最终生成要执行的sql。

MappedStatement生成完成后,执行configuration.addMappedStatement(statement),加入到configuration。

总结:到这里我们就知道了xml的配置根据标签,一个一个去解析。解析完成后,最终会生成MappedStatement对象,然后把它添加到configuration。整个解析完成后,configuration是不是就拥有了所有xml的配置信息了,包括:sql语句,以及sql的返回值字段到对象的映射关系。这些sql执行和sql结果映射需要的东西,全部都在configuration里面了。

下一节准备分析:

  1. Mybatis中Sql解析执行的原理是什么?
  2. Mybatis中Executor接口有几种实现方式

未完待续。。。

三、Mybatis中Sql解析执行的原理是什么?

3.1 先来介绍sql 解析的原理

可以看下面的这篇文章:
Mybatis解析动态sql原理分析

其实上面讲的更加透彻,从sqlNode的多个实现类来解释。mybatis根据不同标签,把sql语句切分不同部分,然后对各个部分分别处理。如果是静态文本,就用StaticTextSqlNode,如果是if标签用IfSqlNode,其他类推。就像上一节截图那样,最终sql语句会被解析问sqlSource,传递给configuration。

3.2 再来介绍sql 执行的原理

当然可以先看这个文章:mybatis调用过程
上面的文章是从mapper整个调用过程来切入的,这节我们只介绍sql具体执行,是其中的一小块内容。首先我们从下面的源码切入:

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

第一步肯定就是生成动态sql了,如下:

public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }

继续跟进getBoundSql方法:

@Override
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

rootSqlNode.apply(context)一般情况是MixedSqlNode,进入该类:

@Override
  public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }

会根据sql语句不同部分,分别调用apply。apply方法完成后,最终会成为一个如下的sql语句:

select count(1) from xxx e left join yyy on e.order_no = rff.biz_no
        where e.sob_id = #{xxx} and e.deal_date >= #{yyy} and e.deal_date <= #{dealDateEnd} and e.active=1 

但是参数值并没有写进去,说明还不是最终要执行的sql。然后由sqlSourceParse.parse方法,会把所有'#{‘开头的匹配出来,变成'?',得到如下的sql:

select count(1) from xxx e left join yyyy rff on e.order_no = rff.biz_no
        where e.sob_id = ? and e.deal_date >= ? and e.deal_date <= ? and e.active=1  
            and e.staff_id = ?

这个sql还是没有参数,还不是最终执行的sql。但是它得到sqlSource变成了StaticSqlSource类型了,因为动态sql其实已经构造完成,它就变成了静态的了,只需要绑定参数即可。

 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);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

到这里传递进来的BoundSql都是带有"?"的sql语句,看下prepareStatement方法:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

参数的绑定就发生在 handler.parameterize(stmt)里面,它会匹配参数里面的key和查询条件的key,然后把对应的value构造到sql中。
就不深入进去了,太多了。。。

至于sql的执行,mybatis解析完成动态sql后,它的职责就完成了。剩下的就交给dataSource的Connection去真正执行网络请求,通过JDBC组件,构建mysql应用能够识别的应用层协议报文,发送给服务器。然后服务器查询得到结果后,返回给mybatis。

JDBC是如何初始化连接的,如何握手验证密码的,以及如何调用的,并不在本文讨论范围。
当然JDBC里面有一堆控制,比如超时,重试,事务等等控制,并不仅仅只有网络传输。

四、Mybatis如果进行ORM转换,把数据库返回的column-value对象,通过映射转换为我们POJO呢?

我们如下的代码:

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

ps.execute(),这里应该是阻塞式调用。函数返回,表示ps已经收到结果了。如果对JDBC是真正如何执行sql的,强烈建议跟进去execute方法,看看mysql厂商的驱动是怎么构造mysql应用层协议的。然后协议的响应,你在客户端肯定是看不着的,你得去研究mysql的源码才能知道。
我们这里就不展开JDBC了。剩下的我们就开始处理返回的结果,mybatis如何映射为POJO?
很明显转换就在handleResultSets方法。
最后给调用handleRowValues方法:

 public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

最终到typeHandler.getResult(rsw.getResultSet(), columnName)方法:

typeHandler.getResult(rsw.getResultSet(), columnName)

typeHandler有很多实现类,比如:IntegerTypeHandler,ObjectTypeHandler等等,它会那映射关系,一个一个取出对应列的值。

END

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343