Mybatis源码阅读8 --- sql语句的一切(1)

前几篇主要从外表看mybatis,这次就要拿起手术刀,一点一点细细看。本篇主要把所有sql语句的写法、解析、执行过程进行分析,篇幅会略长,主要有个方面内容:crud及动态sql的使用,sql解析及执行,之前的专题中若和内容核心内容相关性不大则被mark下,不过从此以后,和核心内容无关的也会深入研究下。

1:CityMapper.xml的sql配置

<?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.hhg.jerry.dao.CityDao">
    <resultMap id="cityResultMap" type="City">
        <id property="id" column="ID" javaType="long" jdbcType="INTEGER" />
        <result property="name" column="Name"/>
        <result property="countryCode" column="CountryCode"/>
        <result property="district" column="district"/>
        <result property="population" column="population"/>
    </resultMap>
    <cache blocking="true" eviction="LRU" flushInterval="500" readOnly="true" size="100" type="perpetual"/>
    <select id="getById" resultMap="com.hhg.jerry.dao.CityDao.cityResultMap">
        SELECT * FROM city where id = #{id}
    </select>

    <select id="getByNameAndCountryCode" resultType="City">
        SELECT * FROM city where Name = #{name} and CountryCode = #{cCode,javaType=string,jdbcType=VARCHAR}
    </select>

    <select id="getByName" resultType="City">
        SELECT * FROM city where Name like '%${name}%'
    </select>

    <select id="getCityAsMapById" resultType="java.util.Map">
        SELECT * FROM city where id = #{id}
    </select>

    <select id="getListLTId" resultType="City" parameterType="java.lang.Long"
    fetchSize="20">
        select * from city where id <= #{maxId}
    </select>

    <select id="getListCDATALtId" resultType="City" parameterType="java.lang.Long">
        select * from city where id  <![CDATA[<=]]> #{maxId}
    </select>

    <select id="getListBetweenIds" resultType="City">
        select * from city where id between #{arg0} and #{arg1} order by ${arg2}
    </select>

    <select id="getCityMappedById" resultType="City">
        select * from city where id <= #{maxId}
    </select>
    
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into city(Name,CountryCode,District,Population) values (#{name}, #{countryCode}, #{district}, #{population})
    </insert>

    <update id="update" parameterType="City">
        update city set Name=#{name},CountryCode=#{countryCode},District=#{district},Population=#{population} where ID = #{id}
    </update>

    <delete id="delete">
        delete from city where ID = #{id}
    </delete>

    <select id="getCityIf" resultType="City">
        select * from city where CountryCode=#{countryCode}
        <if test="district != null">
            and District like #{district}
        </if>
    </select>

    <select id="getCityIfLike" resultType="City">
        <bind name="likeDistrict" value="'%' + district + '%'" />
        select * from city where CountryCode=#{countryCode}
        <if test="district != null">
            and District like #{likeDistrict}
        </if>
    </select>

    <select id="getCityChoose" resultType="City" parameterType="City">
        select * from city where CountryCode=#{countryCode}
        <choose>
            <when test="name != null">
                and Name = #{name}
            </when>
            <when test="district != null">
                and District = #{district}
            </when>
            <otherwise>
                and Name = 'Peking'
            </otherwise>
        </choose>
    </select>

    <select id="getCityWhere" resultType="City" parameterType="City">
        select * from city
        <where>
            <if test="countryCode != null">CountryCode=#{countryCode}</if>
            <if test="name != null">and Name=#{name}</if>
            <if test="district != null">and District=#{district}</if>
        </where>
    </select>

    <select id="getCityTrim" resultType="City" parameterType="City">
        select * from city
        <trim prefix="WHERE" prefixOverrides="AND |OR ">
            <if test="countryCode != null">CountryCode=#{countryCode}</if>
            <if test="name != null">and Name=#{name}</if>
            <if test="district != null">and District=#{district}</if>
        </trim>
    </select>

    <update id="updateSet" parameterType="City">
        update city
        <set>
            <if test="name != null">Name=#{name},</if>
            <if test="countryCode != null">CountryCode=#{countryCode},</if>
            <if test="district != null">District=#{district},</if>
            <if test="population != null">Population=#{population}</if>
        </set>
        where ID = #{id}
    </update>

    <update id="updateTrim" parameterType="City">
        update city
            <trim prefix="SET" suffixOverrides=",">
                    <if test="name != null">Name=#{name},</if>
                    <if test="countryCode != null">CountryCode=#{countryCode},</if>
                    <if test="district != null">District=#{district},</if>
                    <if test="population != null">Population=#{population}</if>
            </trim>
        where ID = #{id}
    </update>

    <!-- index是集合索引,item为该索引的元素 -->
    <select id="getCityForEachList" resultType="City" parameterType="java.util.List">
        select * from city where ID in
        <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
            #{item},#{index}
        </foreach>
    </select>

    <!-- index是集合索引,item为该索引的元素 -->
    <select id="getCityForEachArray" resultType="City">
        select * from city where ID in
        <foreach item="item" index="index" collection="array" open="(" separator="," close=")">
            #{item},#{index}
        </foreach>
    </select>

    <!-- index是集合索引,item为该索引的元素 -->
    <select id="getCityForEachSet" resultType="City" parameterType="java.util.Set">
        select * from city where ID in
        <foreach item="item" index="index" collection="collection" open="(" separator="," close=")">
            #{item},#{index}
        </foreach>
    </select>

    <select id="getCityForEachMap" resultType="City">
        select * from city where ID in
        <foreach item="item" index="index" collection="ids" open="(" separator="," close=")">
            #{item},#{index}
        </foreach>
    </select>
</mapper>

差不多就这些,对每个方法建议先自行实验下,在配置过程中,肯定会有很多疑惑,如resultMap元素下result标签有javaType,jdbcType,sql语句标签属性有resultMap或resultType,parameterType,#{param}中可添加javaType,jdbcType也可以不加,insert如何返回id,如何返回map,如何批量插入,${}和#{}有什么不同等等等等,接下来从源码中发现这些问题的答案吧。

先定位到解析mapper的sql语句代码,XMLStatementBuilder的parseStatementNode方法,贴下这个方法(context就是sql语句的xml元素):

 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);
  }

挑出几个需要说明的:

fetchSize,从数据库一次获取多少条记录(比如select出了1w条,每次我取100条),不同的jdbc支持不同,有兴趣可搜索下深入研究

StatementType:枚举,有STATEMENT,PREPARED,CALLABLE,就是JDBC可创建的statement对象,默认是PREPARED(JDBC中:connection.prepareStatement())

ResultSetType : ResultSet类型,默认是Forward_only(JDBC中,while(resultSet.next()),其它两种好像支持向前迭代等功能,同样有兴趣可进一步研究

XMLInCludeTransformer被我无情的忽视了安静

processSelectKeyNodes也同上,它和id生成有关吧。。。

KeyGenerator, 帮你insert时插入id

MappedStatement,解析后都保存在我这

SqlSource,parsetStatementNode方法的主角,sql语句信息全在我这里,看下SqlSource:

public interface SqlSource {

  BoundSql getBoundSql(Object parameterObject);

}

哇,就一个方法的接口...有3个实现类(还有一个deprecated了)DynamicSqlSource,RawSqlSource,StaticSqlSource,接下来看如何构建SqlSource,langDriver中RawLanguageDriver继承XMLLanguageDriver且Raw的注释以说明(你没有必要用我了),那就直接移步XMLLanguageDriver的方法:

  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }

parameterType就是sql元素的属性,不配置就为null,再移步XMLScriptBuiler:

  public SqlSource parseScriptNode() {
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

这个方法需要看仔细喽,先解析sql节点为MixedSqlNode,再跟进是否为dynamic来生成DynamicSqlSource或者RawSqlSource,DynamicSqlSource构造函数:

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

啥也没做,只是保存了构造参数,RawSqlSource整个类搬过来了:

public class RawSqlSource implements SqlSource {

  private final SqlSource sqlSource;

  public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }

  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
  }

  private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    DynamicContext context = new DynamicContext(configuration, null);
    rootSqlNode.apply(context);
    return context.getSql();
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    return sqlSource.getBoundSql(parameterObject);
  }

}

先获取sql语句,在调用另外一个构造函数,改方法中,生成了sqlSource,到底生成啥?

  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

StaticSqlSource,就是它。回过头来看看生成MixedSqlNode的方法:

  protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }

MixedSqlNode是啥?实现了SqlNode接口,SqlNode接口:

public interface SqlNode {
  boolean apply(DynamicContext context);
}

该方法就是转换sql。生成SqlSource就是上面的几个函数,生成后如何使用呢?BaseExecutor的query方法:

  @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);
 }

这里先通过MappedStatement拿到BoundSql,其是就是调用SqlSource的getBoundSql方法,boundSql再哪里使用呢?

public class DefaultParameterHandler implements ParameterHandler {

  private final TypeHandlerRegistry typeHandlerRegistry;

  private final MappedStatement mappedStatement;
  private final Object parameterObject;
  private final BoundSql boundSql;
  private final Configuration configuration;

  public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    this.mappedStatement = mappedStatement;
    this.configuration = mappedStatement.getConfiguration();
    this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    this.parameterObject = parameterObject;
    this.boundSql = boundSql;
  }

  @Override
  public Object getParameterObject() {
    return parameterObject;
  }

  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

}

跟代码发现boundSql保存在DefaultParameterHandler,这个类中的方法setParameter方法就是通过boundSql保存的信息来设置sql语句的参数(JDBC问号?设置参数)。整个sql解析、执行流程已经分析完,下面具体从不同的sql语句构建SqlSource,先从简单的入手:

    <select id="getById" resultMap="com.hhg.jerry.dao.CityDao.cityResultMap">
        SELECT * FROM city where id = #{id}
    </select>

直接到生成SqlSource处,类XMLScriptBuilder:

  public SqlSource parseScriptNode() {
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

  protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }

先看生成MixedSqlNode,nodeType如果是TEXT_NODE,则构建TextSqlNode,并判断是否是动态,若不是,则构建StaticTextSqlNode,此处判断是否为动态sql,通过debug发现如果sql语句有${},则认为是动态。这里getById为非动态,则生成RawSqlSource,前面分析过RawSqlSource会构建StaticSqlSource,还是贴出来代码吧:

  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

这个方法就是解析token(这里token是#{,})当遇见左右token,parse(GenericTokenParse)就把token中间的文本交给handler(ParameterMappingTokeHandler)处理,并吧handler返回的文本append到builder中(ParameterMappingTokenHandler返回?),handler在处理context(token之间的文本,id)会构建ParameterMapping,看下这个类定义得知它保存的参数相关的信息(java类型,typeHandler,小数级别等),生成方法:

private ParameterMapping buildParameterMapping(String content) {
      Map<String, String> propertiesMap = parseParameterMapping(content);
      String property = propertiesMap.get("property");
      Class<?> propertyType;
      if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
        propertyType = metaParameters.getGetterType(property);
      } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
        propertyType = parameterType;
      } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
        propertyType = java.sql.ResultSet.class;
      } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
        propertyType = Object.class;
      } else {
        MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
        if (metaClass.hasGetter(property)) {
          propertyType = metaClass.getGetterType(property);
        } else {
          propertyType = Object.class;
        }
      }
      ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
      Class<?> javaType = propertyType;
      String typeHandlerAlias = null;
      for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
        String name = entry.getKey();
        String value = entry.getValue();
        if ("javaType".equals(name)) {
          javaType = resolveClass(value);
          builder.javaType(javaType);
        } else if ("jdbcType".equals(name)) {
          builder.jdbcType(resolveJdbcType(value));
        } else if ("mode".equals(name)) {
          builder.mode(resolveParameterMode(value));
        } else if ("numericScale".equals(name)) {
          builder.numericScale(Integer.valueOf(value));
        } else if ("resultMap".equals(name)) {
          builder.resultMapId(value);
        } else if ("typeHandler".equals(name)) {
          typeHandlerAlias = value;
        } else if ("jdbcTypeName".equals(name)) {
          builder.jdbcTypeName(value);
        } else if ("property".equals(name)) {
          // Do Nothing
        } else if ("expression".equals(name)) {
          throw new BuilderException("Expression based parameters are not supported yet");
        } else {
          throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + parameterProperties);
        }
      }
      if (typeHandlerAlias != null) {
        builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
      }
      return builder.build();
    }

纯文本解析,由于我们的参数没有指定javaType和jdbcType,它会认为javaType是Object,jdbcType也会生成一个UnkownTypeHandle:

    private void resolveTypeHandler() {
      if (parameterMapping.typeHandler == null && parameterMapping.javaType != null) {
        Configuration configuration = parameterMapping.configuration;
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        parameterMapping.typeHandler = typeHandlerRegistry.getTypeHandler(parameterMapping.javaType, parameterMapping.jdbcType);
      }
    }

至此我们的getById就解析完成了,继续分析下一个sql解析之前,我们先总结一下getById:

构建mapper -> 构建Statement -> 构建SqlSource -> 构建MixedSqlNode -> (由于非动态)构建RawSqlSource -> 构建StaticSqlSource

需要注意的点:

构建MixedSqlNode时我们的children(node.getChildNodes())的length就是1,且nodetype为Node.TEXT_NODE,所以生成了TextSqlNode,继而根据TextSqlNode继续判断是否是动态,不是的话就生成StaticTextSqlNode,并加到队列中作为MixedSqlNode的构造函数参数。这里的TextSqlNode判断是否动态直接判断sql语句是否包含${},通过返回的MixedSqlNode继续判断是否动态,不是的话就生成RawSqlSource,RawSqlSource直接把SqlNode转换为sql语句,how? 还记得SqlNode的接口方法吗?apply(DynamicContext context)就会把sql放置到context的StringBuilder中(暂时只能看到StaticSqlSource是这么做的),此时sql仍然是select * from city where id = #{id},在通过SqlSourceParse把#{}转换为“?”,并通过ParameterMappingHandler收集#{}里面的信息并存在ParameterMapping的集合中。

select中配置的resultMap何时解析呢? 额,SqlSource表示:虽然传入给我的是context(整个sql节点元素),但并不归我管,是构建MappedStatement解析的,我只是用了sql元素中的parameterType属性。

上面这一坨其是还没分析完,构建SqlSource,缺什么呢?如何解析参数,如何使用SqlSource,回到解析参数,我们有两种方式调用这个方法,其一是直接调用接口方法CityDao.getById(Long),另外是sqlSession.selectOne,先看接口方法,goto there:


特殊param,跳过,注解,跳过,userActualParamName,默认为true,getActualParamName里面会判断是否有Reflect这个类(1.8的),如果有,我们的参数就叫arg0,没有就是0,接着看MapperMethod中转换参数的方法convertArgsToSqlCommandParam:


一个参数就返回了,从这里看#{id}并没有啥用,你也可以写成#{what},然后就调用了sqlSession.selectOne方法,证实了缺失可以随意写.继续跟到设置参数处:


parameterObject就是传入的Long,value就是这个参数,直接通过typeHandler(这里是UnKnownTypeHandler)给prepareStatement设置参数的值,虽然是UnKnowTypeHandler:

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
      throws SQLException {
    TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
    handler.setParameter(ps, i, parameter, jdbcType);
  }

但还是会根据parameter的type找到typeHandler(这里是LongTypeHandler)再来设置参数:

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setLong(i, parameter);
  }
好了,还是那个方法getById就分析了这么多,不过接下来的方法和该方法类似,抬走,下一个

猜你喜欢

转载自blog.csdn.net/lnlllnn/article/details/80934672