mybatis源码(六)mybatis动态sql的解析过程上篇

mybatis源码(六)mybatis动态sql的解析过程上篇

  mybaits支持动态sql的使用。常见的动态sql标签:<where></where>标签、<if></if>、<choose|when|otherwise>、<foreach>、<trim>、<set>

  1.组件介绍

  1.1 SqlSource :用于描述MyBatis中的SQL资源信息。是个接口,只有一个方法

public interface SqlSource {

  BoundSql getBoundSql(Object parameterObject);

}

  由以下几个实现类 这4种SqlSource实现类的作用如下。

  ● ProviderSqlSource: 用于描述通过@Select、@SelectProvider等注解配置的SQL资源信息。
  ● DynamicSqlSource: 用于描述Mapper XML文件中配置的SQL资源信息,这些SQL通常包含动态SQL配置或者${}参数占位符,需要在Mapper调用时才能确定具体的SQL语句。
  ● RawSqlSource: 用于描述Mapper XML文件中配置的SQL资源信息,与DynamicSqlSource不同的是,这些SQL语句在解析XML配置的时候就能确定,即不包含动态SQL相关配置。
  ● StaticSqlSource: 用于描述ProviderSqlSource、DynamicSqlSource及RawSq|Source解析后得到的静态SQL资源。

  配置sql信息的两种方式:1.注解的方式:@SELECT @INSERT @Delete等  2.通过xml配置文件的方式

  1.2 BoundSql : BoundSql是对动态SQL解析生成的SQL语句和参数映射信息的封装.是个class类

  源码如下:

public class BoundSql {

  // Mapper配置解析后的sql语句
  private final String sql;
  // Mapper参数映射信息
  private final List<ParameterMapping> parameterMappings;
  // Mapper参数对象
  private final Object parameterObject;
  // 额外参数信息,包括<bind>标签绑定的参数,内置参数
  private final Map<String, Object> additionalParameters;
  // 参数对象对应的MetaObject对象
  private final MetaObject metaParameters;

  public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.parameterObject = parameterObject;
    this.additionalParameters = new HashMap<String, Object>();
    this.metaParameters = configuration.newMetaObject(additionalParameters);
  }

  public String getSql() {
    return sql;
  }

  public List<ParameterMapping> getParameterMappings() {
    return parameterMappings;
  }

  public Object getParameterObject() {
    return parameterObject;
  }

  public boolean hasAdditionalParameter(String name) {
    String paramName = new PropertyTokenizer(name).getName();
    return additionalParameters.containsKey(paramName);
  }

  public void setAdditionalParameter(String name, Object value) {
    metaParameters.setValue(name, value);
  }

  public Object getAdditionalParameter(String name) {
    return metaParameters.getValue(name);
  }
}

     1.3 LanguageDriver : 用于解析SQL配置,将SQL 配置信息转换为SqlSource对象

    LanguageDriver 接口中一共有3个方法,其中createParameterHandler()方法用于创建ParameterHandler对象,另外还有两个重载的createSqlSource()方法,这两个重载的方法用于创建Sq|Source对象。有两个实现类:

      XMLanguageDriver : 为XML语言驱动,为MyBatis提供了通过XML标签(我们常用的<if>、<where>等标签)结合OGNL表达式语法实现动态SQL的功能。

        a.java注解也可使用动态sql,但是sql语句中要加入<script></script>标签括起来

        b.java注解方式演示动态sql代码 

@Data
public class UserEntity {
    private Long id;
    private String name;
    private Date createTime;
    private String password;
    private String phone;
    private String nickName;
}
 

public interface UserMapper {

    List<UserEntity> getUserByEntity(UserEntity user);

    List<UserEntity> getUserInfo(UserEntity user);

    List<UserEntity> getUserByPhones(@Param("phones") List<String> phones);

    @Select("<script>" +
            "select * from user
" +
            "<where>
" +
            "    <if test="name != null">
" +
            "        AND name = #{name}
" +
            "    </if>
" +
            "    <if test="phone != null">
" +
            "        AND phone = #{phone}
" +
            "    </if>
" +
            "</where>" +
            "</script>")
    UserEntity getUserByPhoneAndName(@Param("phone") String phone, @Param("name") String name);


    List<UserEntity> getUserByNames(@Param("names") List<String> names);

    UserEntity getUserByName(@Param("userName") String userName);
}
 
 
 
View Code

      RawLanguageDriver : 表示仅支持静态SQL配置,不支持动态SQL功能

        c.MyBatis从3.2版本开始支持可插拔脚本语言,这允许我们插入一种脚本语言驱动,并基于这种语言来编写动态SQL语句。例如,我们可以让MyBatis的Mapper配置支持Velocity (或者Freemaker)语法,并基于Velocity (或者Freemaker)           语法编写动态SQL。要实现自定义的脚本语言驱动,只需要实现LanguageDriver接口,创建自定义的SqlSource对象,然后对SqlSource对象进行解析,生成最终的BoundSq|对象即可。首先需要在项目中添加该模块的依赖.

<dependency>
    <groupId>org.mybatis.scripting</groupId>
    <artifactId>mybatis-velocity</artifactId>
    <version>2.0-SNAPSHOT</version>
</dependency>

    1.4SqlNode用于描述动态SQL中<if>、<where>等标签信息,LanguageDriver解析SQL配置时,会把<if>、<where> 等动态SQL标签转换为SqINode对象,封装在Sq|Source中。

    SqlNode是一个接口,只有一个方法,源码如下:

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

  提供了10种动态sql的实现类:

  ●   IfSqINode: 用于描述动态SQL中<if>标签的内容,XMLanguageDriver在解析Mapper SQL配置生成SqlSource时,会对动态SQL中的<if>标签进行解析,将<if>标签转换为lfSqlNode对象。

  ●   ForEachSqlNode: 用于描述动态SQL配置中的<foreach>标签,<foreach>标签配置信息在Mapper解析时会转换为ForEachSqlNode对象。

  ●  ChooseSqlINode: 用于描述动态SQL配置中的<choose>标签内容,Mapper解析时会把<choose>标签配置内容转换为ChooseSqINode对象。 

  ●  MixedSqlNode: 用于描述一组SqINode对象, 通常一个Mapper配置是由多个SqlNode对象组成的,这些SqlNode对象通过MixedSqlNode进行关联,组成一个完整的动态SQL配置。

  ●  SetSqlNode: 用于描述动态SQL配置中的<set>标签,Mapper解析时会把<set>标签配置信息转换为SetSqINode对象。 

  ● TrimSq|Node: 用于描述动态SQL中的<trim>标签,动态SQL解析时,会把<trim>标签内容转 换为TrimSqlNode对象。在MyBatis动态SQL使用时,<where>标签和
    <set>标签实现的内容都可以使用<trim>标签来完成,因此WhereSqlNode和SetSqlNode类设计为TrimSqlNode类的子类,属于特殊的TrimSqlNode。

  ●  StaticTextSqlNode: 用于描述动态SQL中的静态文本内容。 

  ● TextSqlNode: 该类与StaticTextSqlNode类不同的是,当静态文本中包含${}占位符时,说明${}需要在Mapper调用时将${}替换为具体的参数值。因此,使用TextSqlNode类来描述。 

  ● VarDecISqlNode: 用于描述动态SQL中的<bind>标签,动态SQL解析时,会把<bind>标签配置信息转换为VarDeclSqlNode对象。

  ● WhereSq|INode: 用于描述动态SQL中的<where>标签,动态SQL解析时,会把<where>标签内容转换为WhereSqINode对象。

      

  用测试代码描述sqlNode的使用过程

@Data
public class UserEntity {
    private Long id;
    private String name;
    private Date createTime;
    private String password;
    private String phone;
    private String nickName;
}
<sql id="userAllField">
  id,create_time, name, password, phone, nick_name
</sql>
 
<select id="getUserInfo"  resultType="com.blog4java.mybatis.example.entity.User">
    select
    <include refid="userAllField"/>
    from user where 1 = 1
    <choose>
        <when test="id != null">
          AND  id = #{id}
        </when>
        <when test="name != null">
            AND  name = #{name}
        </when>
        <otherwise>
            AND phone is not null
        </otherwise>
    </choose>
</select>

 
@Test
public void testSqlNode() {
    // 构建SqlNode
    SqlNode sn1 = new StaticTextSqlNode("select * from user where 1=1");
    SqlNode sn2 = new IfSqlNode(new StaticTextSqlNode(" AND id = #{id}"),"id != null");
    SqlNode sn3 = new IfSqlNode(new StaticTextSqlNode(" AND name = #{name}"),"name != null");
    SqlNode sn4 = new IfSqlNode(new StaticTextSqlNode(" AND phone = #{phone}"),"phone != null");
    SqlNode mixedSqlNode = new MixedSqlNode(Arrays.asList(sn1, sn2, sn3, sn4));
    // 创建参数对象
    Map<String, Object> paramMap = new HashMap<>();
    paramMap.put("id","1");
    // 创建动态SQL解析上下文
    DynamicContext context = new DynamicContext(sqlSession.getConfiguration(),paramMap);
    // 调用SqlNode的apply()方法解析动态SQL
    mixedSqlNode.apply(context);
    // 调用DynamicContext对象的getSql()方法获取动态SQL解析后的SQL语句
    System.out.println(context.getSql());
}
View Code

  1.5  NodeHandler:这是一个接口,提供了8种实现类。每种处理器用于处理对应的sql的动态标签。例如IfHandler用于处理动态sql配置中的<if>标签。负责将<if>标签内容转换为ifSqlNode对象

  八种实现类分别是:BindHandler、TrimHandlerWhereHandlerSetHandlerForEachHandlerIfHandlerOtherwiseHandlerChooseHandler

public class XMLScriptBuilder extends BaseBuilder {

  private final XNode context;
  private boolean isDynamic;
  private final Class<?> parameterType;
  private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<String, NodeHandler>();

  public XMLScriptBuilder(Configuration configuration, XNode context) {
    this(configuration, context, null);
  }

  public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
    super(configuration);
    this.context = context;
    this.parameterType = parameterType;
    initNodeHandlerMap();
  }


  private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
  }

  public SqlSource parseScriptNode() {
    // 调用parseDynamicTags()方法將SQL配置转换为SqlNode对象
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
    // 判断Mapper SQL配置中是否包含动态SQL元素,如果是创建DynamicSqlSource对象,否则创建RawSqlSource对象
    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();
    // 对XML子元素进行遍历
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      // 如果子元素为SQL文本内容,则使用TextSqlNode描述该节点
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        // 判断SQL文本中包含${}参数占位符,则为动态SQL
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          // 如果SQL文本中不包含${}参数占位符,则不是动态SQL
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) {
        // 如果子元素为<if>、<where>等标签,则使用对应的NodeHandler处理
        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);
  }

  private interface NodeHandler {
    void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
  }

  private class BindHandler implements NodeHandler {
    public BindHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      final String name = nodeToHandle.getStringAttribute("name");
      final String expression = nodeToHandle.getStringAttribute("value");
      final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
      targetContents.add(node);
    }
  }

  private class TrimHandler implements NodeHandler {
    public TrimHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      String prefix = nodeToHandle.getStringAttribute("prefix");
      String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
      String suffix = nodeToHandle.getStringAttribute("suffix");
      String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
      TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
      targetContents.add(trim);
    }
  }

  private class WhereHandler implements NodeHandler {
    public WhereHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
      targetContents.add(where);
    }
  }

  private class SetHandler implements NodeHandler {
    public SetHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
      targetContents.add(set);
    }
  }

  private class ForEachHandler implements NodeHandler {
    public ForEachHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      // 首先调用parseDynamicTags()方法解析<foreach>标签子元素
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      String collection = nodeToHandle.getStringAttribute("collection");
      String item = nodeToHandle.getStringAttribute("item");
      String index = nodeToHandle.getStringAttribute("index");
      String open = nodeToHandle.getStringAttribute("open");
      String close = nodeToHandle.getStringAttribute("close");
      String separator = nodeToHandle.getStringAttribute("separator");
      ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);
      targetContents.add(forEachSqlNode);
    }
  }

  private class IfHandler implements NodeHandler {
    public IfHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      // 继续调用parseDynamicTags()方法解析<if>标签中的子节点
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      // 获取<if>标签test属性
      String test = nodeToHandle.getStringAttribute("test");
      // 创建IfSqlNode对象
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      // 將IfSqlNode对象添加到List中
      targetContents.add(ifSqlNode);
    }
  }

  private class OtherwiseHandler implements NodeHandler {
    public OtherwiseHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      targetContents.add(mixedSqlNode);
    }
  }

  private class ChooseHandler implements NodeHandler {
    public ChooseHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      List<SqlNode> whenSqlNodes = new ArrayList<SqlNode>();
      List<SqlNode> otherwiseSqlNodes = new ArrayList<SqlNode>();
      handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
      SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
      ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
      targetContents.add(chooseSqlNode);
    }

    private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) {
      List<XNode> children = chooseSqlNode.getChildren();
      for (XNode child : children) {
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler instanceof IfHandler) {
          handler.handleNode(child, ifSqlNodes);
        } else if (handler instanceof OtherwiseHandler) {
          handler.handleNode(child, defaultSqlNodes);
        }
      }
    }

    private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
      SqlNode defaultSqlNode = null;
      if (defaultSqlNodes.size() == 1) {
        defaultSqlNode = defaultSqlNodes.get(0);
      } else if (defaultSqlNodes.size() > 1) {
        throw new BuilderException("Too many default (otherwise) elements in choose statement.");
      }
      return defaultSqlNode;
    }
  }

}
View Code

  1.6 GenericTokenParser:Token解析器,用于解析#{}参数,获取#{}参数占位符中内容   

       例如下面是一个可能的#{}参数占位符配置 :#{userld,javaType=long,jdbcType=NUMERIC,typeHandler=MyTypeHandler} 该参数占位符经过GenericTokenParser解析后,获取参数占位符内容,即          userld,javaType=long,jdbcType=NUMERIC,typeHandler=MyTypeHandler,该内容会经过ParameterMappingTokenHandler对象进行替换处理。

  

public class GenericTokenParser {

  private final String openToken;
  private final String closeToken;
  private final TokenHandler handler;

  public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
  }

  public String parse(String text) {
    if (text == null || text.isEmpty()) {
      return "";
    }
    // 获取第一个openToken在SQL中的位置
    int start = text.indexOf(openToken, 0);
    // start为-1说明SQL中不存在任何参数占位符
    if (start == -1) {
      return text;
    }
    // 將SQL转换为char数组
    char[] src = text.toCharArray();
    // offset用于记录已解析的#{或者}的偏移量,避免重复解析
    int offset = 0;
    final StringBuilder builder = new StringBuilder();
    // expression为参数占位符中的内容
    StringBuilder expression = null;
    // 遍历获取所有参数占位符的内容,然后调用TokenHandler的handleToken()方法替换参数占位符
    while (start > -1) {
      if (start > 0 && src[start - 1] == '\') {
        // this open token is escaped. remove the backslash and continue.
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start + openToken.length();
      } else {
        // found open token. let's search close token.
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        builder.append(src, offset, start - offset);
        offset = start + openToken.length();
        int end = text.indexOf(closeToken, offset);
        while (end > -1) {
          if (end > offset && src[end - 1] == '\') {
            // this close token is escaped. remove the backslash and continue.
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end + closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {
            expression.append(src, offset, end - offset);
            offset = end + closeToken.length();
            break;
          }
        }
        if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {
          // 调用TokenHandler的handleToken()方法替换参数占位符
          builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();
        }
      }
      start = text.indexOf(openToken, offset);
    }
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }

    return builder.toString();
  }
}

如上面的代码所示,在GenericTokenParser的parse()方法中, 对SQL配置中的所有#{}参数占位符进行解析,获取参数占位符的内容,然后调用ParameterMappingTokenHandler的handleToken()方法对参
数占位符内容进行替换。参数占位符的替换过程 ->ParameterMappingTokenHandler的handleToken()方法

1.7 ParameterMappingTokenHandlerParameterMappingTokenHandler为Mybatis参数映射处理器,用于处理SQL中的#{}参数占位符

public class SqlSourceBuilder extends BaseBuilder {

  private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";

  public SqlSourceBuilder(Configuration configuration) {
    super(configuration);
  }
  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    // ParameterMappingTokenHandler为Mybatis参数映射处理器,用于处理SQL中的#{}参数占位符
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    // Token解析器,用于解析#{}参数
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    // 调用GenericTokenParser对象的parse()方法將#{}参数占位符转换为?
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }
  private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

    private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
    private Class<?> parameterType;
    private MetaObject metaParameters;

    public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
      super(configuration);
      this.parameterType = parameterType;
      this.metaParameters = configuration.newMetaObject(additionalParameters);
    }

    public List<ParameterMapping> getParameterMappings() {
      return parameterMappings;
    }

    @Override
    public String handleToken(String content) {
      parameterMappings.add(buildParameterMapping(content));
      return "?";
    }

    private ParameterMapping buildParameterMapping(String content) {
      // 將#{}占位符内容转换为Map对象
      Map<String, String> propertiesMap = parseParameterMapping(content);
      // property 对应的值为参数占位符名称,例如userId
      String property = propertiesMap.get("property");
      // 推断参数类型
      Class<?> propertyType;
      // 如果内置参数,或<bind>标签绑定的参数包含该属性,则参数类型为Getter方法返回值类型
      if (metaParameters.hasGetter(property)) {
        propertyType = metaParameters.getGetterType(property);
        // 判读该参数类型是否注册了TypeHandler,如果注册了则使用参数类型
      } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
        propertyType = parameterType;
        // 如果指定了jdbcType属性,并且为CURSOR类型,则使用ResultSet类型
      } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
        propertyType = java.sql.ResultSet.class;
        // 如果参数类型为Map接口的子类型,则使用Object类型
      } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
        propertyType = Object.class;
      } else {
        // 获取parameterType对应的MetaClass对象,方便获取参数类型的反射信息
        MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
        // 如果参数类型中包含该属性,则使用Getter方法返回类型
        if (metaClass.hasGetter(property)) {
          propertyType = metaClass.getGetterType(property);
        } else {
          propertyType = Object.class;
        }
      }
      // 使用建造者模式构建ParameterMapping对象
      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();
        // 指定ParameterMapping对象的属性
        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));
      }
      // 返回ParameterMapping对象
      return builder.build();
    }

    private Map<String, String> parseParameterMapping(String content) {
      try {
        return new ParameterExpression(content);
      } catch (BuilderException ex) {
        throw ex;
      } catch (Exception ex) {
        throw new BuilderException("Parsing error was found in mapping #{" + content + "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
      }
    }
  }

}

从上面的代码可以看出,SQL配置中的所有#{}参数占位符内容都被替换成了"?" 字符,为什么要替换成一一个"?" 字符呢?
因为MyBatis默认情况下会使用PreparedStatement对象与数据库进行交互,因此#{}参数占位符内容被替换成了问号,然后调用PreparedStatement对象的setXXX()方法为参数占位符设置值。
除此之外,ParameterMappingTokenHandler的handleToken()方法中还做了另一件事情,就是调用buildParameterMapping()方法对占位符内容进行解析,将占位符内容转换为ParameterMapping对象。
ParameterMapping 对象用于描述MyBatis参数映射信息,便于后续根据参数映射信息获取对应的TypeHandler为PreparedStatement对象设置值。
buildParameterMapping()方法解析参数占位符生成ParameterMapping对象的过程也在上述代码中

如上面的代码所示,在ParameterMappingTokenHandler类的buildParameterMapping()方法中首先将参数占位符内容转换为Map对
象,例如参数占位符内容如下:#{userld,javaType=long,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
将会转换成如下Map对象:

Map<String, String> map = new HashMap<>() ;
map .put ("property", "userId") ;
map .put ("javaType", "long") ;
map.put ("jdbcType", "NUMERIC") ;
map.put ("typeHandler", "MyTypeHandler") ;

然后通过一系列的逻辑判断参数的类型(javaType属性值)。最后通过建造者模式构建ParameterMapping对象。

原文地址:https://www.cnblogs.com/yingxiaocao/p/13697067.html