美文网首页技术分享
MyBatis源码阅读【加载】(三)SqlSource创建流程

MyBatis源码阅读【加载】(三)SqlSource创建流程

作者: 云芈山人 | 来源:发表于2021-05-20 00:20 被阅读0次

    一、相关类及对象

    • XMLLanguageDriver

    • XMLScriptBuilder

    • SqlSource接口

    • SqlSourceBuilder

    • DynamicSqlSource

      主要是封装动态SQL标签解析之后的SQL语句和带有${}的SQL语句
    • RawSqlSource

      主要封装带有#{}的SQL语句
    • StaticSqlSource

      是BoundSql中要存储SQL语句的一个载体,上面两个SqlSource
      的SQL语句,最终都会存储到该SqlSource实现类中。

    二、流程图

    SqlSource创建流程.png

    三、SqlSource创建流程

    • 入口:XMLLanguageDriver#createSqlSource

    创建SqlSource,解析SQL,封装SQL语句(未参数绑定)和入参信息

    @Override
        public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
            // 初始化了动态SQL标签处理器
            XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
            // 解析动态SQL
            return builder.parseScriptNode();
        }
    
    • XMLScriptBuilder#构造函数

    初始化了动态SQL标签处理器

    public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
            super(configuration);
            this.context = context;
            this.parameterType = parameterType;
            // 初始化动态SQL中的节点处理器集合
            initNodeHandlerMap();
        }
    

    1.XMLScriptBuilder#initNodeHandlerMap

    初始化动态SQL中的节点处理器集合

    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());
        }
    
    • XMLScriptBuilder#parseScriptNode

    解析动态SQL

    public SqlSource parseScriptNode() {
            // 解析select\insert\ update\delete标签中的SQL语句,最终将解析到的SqlNode封装到MixedSqlNode中的List集合中
            // ****将带有${}号的SQL信息封装到TextSqlNode
            // ****将带有#{}号的SQL信息封装到StaticTextSqlNode
            // ****将动态SQL标签中的SQL信息分别封装到不同的SqlNode中
            MixedSqlNode rootSqlNode = parseDynamicTags(context);
            SqlSource sqlSource = null;
            // 如果SQL中包含${}和动态SQL语句,则将SqlNode封装到DynamicSqlSource
            if (isDynamic) {
                sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
            } else {
                // 如果SQL中包含#{},则将SqlNode封装到RawSqlSource中,并指定parameterType
                sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
            }
            return sqlSource;
        }
    
    • 1 XMLScriptBuilder#parseDynamicTags

    解析select\insert\ update\delete标签中的SQL语句,最终将解析到的SqlNode封装到MixedSqlNode中的List集合中。

    • 将带有${}号的SQL信息封装到TextSqlNode;
    • 将带有#{}号的SQL信息封装到StaticTextSqlNode
    • 将动态SQL标签中的SQL信息分别封装到不同的SqlNode中
    protected MixedSqlNode parseDynamicTags(XNode node) {
            List<SqlNode> contents = new ArrayList<>();
            //获取<select>\<insert>等4个标签的子节点,子节点包括元素节点和文本节点
            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("");
                    // 将文本内容封装到SqlNode中
                    TextSqlNode textSqlNode = new TextSqlNode(data);
                    // SQL语句中带有${}的话,就表示是dynamic的
                    if (textSqlNode.isDynamic()) {
                        contents.add(textSqlNode);
                        isDynamic = true;
                    } else {
                        // SQL语句中(除了${}和下面的动态SQL标签),就表示是static的
                        // StaticTextSqlNode的apply只是进行字符串的追加操作
                        contents.add(new StaticTextSqlNode(data));
                    }
                    
                    //处理元素节点
                } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
                    String nodeName = child.getNode().getNodeName();
                    // 动态SQL标签处理器
                    // 思考,此处使用了哪种设计模式?---策略模式
                    NodeHandler handler = nodeHandlerMap.get(nodeName);
                    if (handler == null) {
                        throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
                    }
                    handler.handleNode(child, contents);
                    // 动态SQL标签是dynamic的
                    isDynamic = true;
                }
            }
            return new MixedSqlNode(contents);
        }
    
    • 2. DynamicSqlSource#构造函数

      如果SQL中包含${}和动态SQL语句,则将SqlNode封装到DynamicSqlSource
    public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
        this.configuration = configuration;
        this.rootSqlNode = rootSqlNode;
    }
    
    • 3. RawSqlSource#构造函数

      如果SQL中包含#{},则将SqlNode封装到RawSqlSource中,并指定parameterType
    private final SqlSource sqlSource;
    
        //先调用 getSql(configuration, rootSqlNode)获取sql,再走下面的构造函数
        public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
            this(configuration, getSql(configuration, rootSqlNode), parameterType);
        }
    
        public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
            // 解析SQL语句
            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();
        }
    
    
    • 3.1 SqlSourceBuilder#parse

      解析SQL语句
    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);
            // 将解析之后的SQL信息,封装到StaticSqlSource对象中
            // SQL字符串是带有?号的字符串,?相关的参数信息,封装到ParameterMapping集合中
            return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
        }
    
    • 3.1.1 ParameterMappingTokenHandler#构造函数

    public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType,Map<String, Object> additionalParameters) {
        super(configuration);
        this.parameterType = parameterType;
        this.metaParameters = configuration.newMetaObject(additionalParameters);
    }
    
    • 3.1.2 GenericTokenParser#构造函数

      创建分词解析器,指定待分析的openToken和closeToken,并指定处理器
     public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
        this.openToken = openToken;
        this.closeToken = closeToken;
        this.handler = handler;
      }
    
    • 3.1.3 GenericTokenParser#parse

      解析SQL语句,处理openToken和closeToken中的内容
    /**
       * 解析${}和#{}
       * @param text
       * @return
       */
      public String parse(String text) {
        if (text == null || text.isEmpty()) {
          return "";
        }
        // search open token
        int start = text.indexOf(openToken, 0);
        if (start == -1) {
          return text;
        }
        char[] src = text.toCharArray();
        int offset = 0;
        final StringBuilder builder = new StringBuilder();
        StringBuilder expression = null;
        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 {
              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();
      }
    
    • 3.1.3.1 ParameterMappingTokenHandler#handleToken

      处理token(#{}/${})
    @Override
    public String handleToken(String content) {
        parameterMappings.add(buildParameterMapping(content));
        return "?";
    }
    
    • 3.1.3.1.1 ParameterMappingTokenHandler#buildParameterMapping

      创建ParameterMapping对象
    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();
    }
    
    • 3.1.2 StaticSqlSource#构造函数

      将解析之后的SQL信息,封装到StaticSqlSource
      private final String sql;
      private final List<ParameterMapping> parameterMappings;
      private final Configuration configuration;
    
      public StaticSqlSource(Configuration configuration, String sql) {
        this(configuration, sql, null);
      }
    
      public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.configuration = configuration;
      }
    

    相关文章

      网友评论

        本文标题:MyBatis源码阅读【加载】(三)SqlSource创建流程

        本文链接:https://www.haomeiwen.com/subject/ybzmjltx.html