• mybatis源码(七)mybatis动态sql的解析过程下篇


    mybatis源码(七)mybatis动态sql的解析过程下篇

      mybatis的MapperStatement的创建过程中,进行的动态sql解析

      XMLStatementBuilder.parseStatementNode()

      public void parseStatementNode() {
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
    
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
          return;
        }
        // 解析<select|update|delete|insert>标签属性
        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");
        // 获取LanguageDriver对象
        String lang = context.getStringAttribute("lang");
        LanguageDriver langDriver = getLanguageDriver(lang);
        // 获取Mapper返回结果类型Class对象
        Class<?> resultTypeClass = resolveClass(resultType);
        String resultSetType = context.getStringAttribute("resultSetType");
        // 默认Statement类型为PREPARED
        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>标签内容,替换为<sql>标签定义的SQL片段
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
        // 解析<selectKey>标签
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
        
        // 通过LanguageDriver解析SQL内容,生成SqlSource对象
        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);
      }
    

    其中是从这里42行的 LanguageDriver 开始的

    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    源码如下:
    public class XMLLanguageDriver implements LanguageDriver {
    
      @Override
      public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
      }
      @Override
      public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
        // 该方法用于解析XML文件中配置的SQL信息
        // 创建XMLScriptBuilder对象
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
        // 调用 XMLScriptBuilder对象parseScriptNode()方法解析SQL资源
        return builder.parseScriptNode();
      }
    
      @Override
      public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
        // 该方法用于解析Java注解中配置的SQL信息
        // 字符串以<script>标签开头,则以XML方式解析
        if (script.startsWith("<script>")) {
          XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
          return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
        } else {
          // 解析SQL配置中的全局变量
          script = PropertyParser.parse(script, configuration.getVariables());
          TextSqlNode textSqlNode = new TextSqlNode(script);
          // 如果SQL中是否仍包含${}参数占位符,则返回DynamicSqlSource实例,否则返回RawSqlSource
          if (textSqlNode.isDynamic()) {
            return new DynamicSqlSource(configuration, textSqlNode);
          } else {
            return new RawSqlSource(configuration, script, parameterType);
          }
        }
      }
    
    }
    

    其中 XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); 的过程。做了标签和对应处理器的初始化工作

    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());
      }
    View Code
    XMLScriptBuilder类parseScriptNode()方法开始了解析工作
      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;
      }

    如上面的代码所示,在XMLScriptBuilder类的parseScriptNode()方法中,调用parseDynamicTags()方法将SQL 配置转换为SqlNode对象,然后判断SQL配置是否为动态SQL,如果为动态SQL,则创建DynamicSqlSource对象,否则创建RawSqlSource对象。需要注意的是,MyBatis中判断SQL配置是否属于动态SQL的标准是SQL配置是否包含<if>、<where>、 <trim> 等元素或者${}参数占位符。DynamicSqlSource这里只是创建了一个对象,并没有做解析工作。解析工作是在执行sql语句的时候完成的。RawSqlSource这里既创建了对象,又实现了sql的解析

    解析部分源代码如下:

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

    其中涉及到了NodeHandler处理器在上一篇中已经提到了。这是一个接口,提供了8种实现类。每种处理器用于处理对应的sql的动态标签。例如IfHandler用于处理动态sql配置中的<if>标签。负责将<if>标签内容转换为ifSqlNode对象。nodeHandlerMap是在XMLScriptBuilder创建的时候,就把标签和对应的处理器初始化完成了

    NodeHandler接口是XMLScriptBuilder内部的一个接口,源码实现如下:

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

    在IfHandler类的handleNode()方法中会继续调用XMLScriptBuilder类的parseDynamicTags()方法完成<if>标签子节点的解析,将子节点转换为MixedSqlNode对象,然后获取<if>标签test属性对应的OGNL表达式,接着创建IfSq|Node对象并添加到List对象中。parseDynamicTags()方法的内容上面已经分析过了,该方法中会获取当前节点的所有子节点,如果子节点内容为动态SQL标签,继续调用动态SQL标签对应的NodeHandler进行处理,这样就"递归”地完成了所有动态SQL标签的解析。

    上面说了动态sql配置解析为SqlNode的过程,那么何时调用mapper,根据传入的参数动态生成sql语句的呢

    继续从XMLScriptBuilder.parseScriptNode()方法开始,代码执行完这个方法之后,sqlSource对象就已经产生了

    动态SQL标签解析完成后,将解析后生成的SqlNode对象封装在SqlSource对象中。MyBatis中的MappedStatement用于描述Mapper中的SQL配置,SqlSource创建完毕后,最终会存放在MappedStatement对象的sqlSource属性中,
    Executor组件操作数据库时,会调用MappedStatement对象的getBoundSql()方法获取BoundSq|对象,部分源代码如下:

    public final class MappedStatement {
    private Cache cache; // 二级缓存实例
      private SqlSource sqlSource; // 解析SQL语句生成的SqlSource实例
      private String resource; // Mapper资源路径
      private Configuration configuration; // Configuration对象的引用
      private KeyGenerator keyGenerator;  // 默认为Jdbc3KeyGenerator,即数据库自增主键,当配置了<selectKey>时,使用SelectKeyGenerator
      private boolean hasNestedResultMaps; // 是否有嵌套的ResultMap
      private Log statementLog; // 输出日志
    
      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;
      }
    }
    

    如上面的代码所示,MappedStatement对象的getBoundSql()方法会调用SqlSource对象的getBoundSq()方法,这个过程就完成了SqlNode对象解析成SQL语句的过程。我们可以了解一下DynamicSqlSource类的getBoundSq()方法的实现,代码如下:

    public class DynamicSqlSource implements SqlSource {
    
      private final Configuration configuration;
      private final SqlNode rootSqlNode;
    
      public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
        this.configuration = configuration;
        this.rootSqlNode = rootSqlNode;
      }
    
      @Override
      public BoundSql getBoundSql(Object parameterObject) {
        // 通过参数对象,创建动态SQL上下文对象
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        // 以DynamicContext对象作为参数调用SqlNode的apply()方法
        rootSqlNode.apply(context);
        // 创建SqlSourceBuilder对象
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        // 调用DynamicContext的getSql()方法获取动态SQL解析后的SQL内容,
        // 然后调用SqlSourceBuilder的parse()方法对SQL内容做进一步处理,生成StaticSqlSource对象
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        // 调用StaticSqlSource对象的getBoundSql()方法,获得BoundSql实例
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        // 將<bind>标签绑定的参数添加到BoundSql对象中
        for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
          boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
        }
        return boundSql;
      }
    
    }
    

    SqlSourceBuilder类的parse()方法对动态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 {
    

    到这里BoundSql的解析就完成了。例如:执行Executor的query方法时,就会根据MapperStatement获取BoundSql,开启动态sql解析过程,部分源码如下:

    BaseExecutor.java

      @Override
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 获取BoundSql对象,BoundSql是对动态SQL解析生成的SQL语句和参数映射信息的封装
        BoundSql boundSql = ms.getBoundSql(parameter);
        // 创建CacheKey,用于缓存Key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        // 调用重载的query()方法
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }
    

    SqlSession是Executor组件的外观,目的是对外提供易于理解和使用的操作数据库的接口,也就是sqlSession调用查询的时候,就会执行上述逻辑

    BaseBuidler是mybatis中xml解析的时候比较重要的抽象类

      

  • 相关阅读:
    字集码(字符编码)
    图片轮播(可实现手动与自动的切换)
    Eclipse常用快捷键
    Java并发编程:Callable、Future和FutureTask
    Java并发之CountDownLatch、CyclicBarrier和Semaphore
    java注解
    JVM加载class原理
    阿里中间件技术及双十一实践--软负载——分布式系统的引路人
    阿里中间件技术及双十一实践--中间件总体介绍
    Java的LockSupport.park()实现分析
  • 原文地址:https://www.cnblogs.com/yingxiaocao/p/13697455.html
Copyright © 2020-2023  润新知