• 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对象。

  • 相关阅读:
    Java的final关键字
    递归
    打开Eclipse时出现"The Eclipse executable launcher was unable to locate its companion shared library"情况的解决
    warning: LF will be replaced by CRLF in test.txt.
    Java类的初始化问题
    递归输入与引用传值(UVa839 Not so Mobile)
    UVa1599 Ideal Path(双向bfs+字典序+非简单图的最短路+队列判重)
    欧拉图和欧拉圈-Play On Words(UVa10129)
    UVA12096 集合栈计算机(map和vector实现双射关系+集合的交并运算的STL)
    WebStorm快捷键
  • 原文地址:https://www.cnblogs.com/yingxiaocao/p/13697067.html
Copyright © 2020-2023  润新知