转载

mybatis 源码分析之 sql 解析

XMLMapperBuilder 当中对 sql 元素的解析:

private void sqlElement(List<XNode> list) throws Exception {     if (configuration.getDatabaseId() != null) {       sqlElement(list, configuration.getDatabaseId());     }     sqlElement(list, null);   }  private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {     for (XNode context : list) {       String databaseId = context.getStringAttribute("databaseId");       String id = context.getStringAttribute("id");       id = builderAssistant.applyCurrentNamespace(id, false);       if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {         sqlFragments.put(id, context);       }     }   }  private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {     if (requiredDatabaseId != null) {       if (!requiredDatabaseId.equals(databaseId)) {         return false;       }     } else {       if (databaseId != null) {         return false;       }       // skip this fragment if there is a previous one with a not null databaseId       if (this.sqlFragments.containsKey(id)) {         XNode context = this.sqlFragments.get(id);         if (context.getStringAttribute("databaseId") != null) {           return false;         }       }     }     return true;   } 

对应的 sql 节点的 XML 如下:

<sql id="" databaseId="">  .... 省略 </sql> 

看上面的代码,主要方法是 sqlElement,循环所有 sql 的 xnode 元素,然后给每个 sql 生成 id(namespace + ‘.’ + id),然后判断 databaseIdMatchesCurrent,来决定是否把这个 xnode 假如到 sqlFragments map 当中。

databaseIdMatchesCurrent 当中的判断是根据这两个 databaseId 是否相同或者都为 null,可以加入到 map 当中。

解析查询更新语句

查看解析查询更新语句的代码:

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {     for (XNode context : list) {       final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);       try {         statementParser.parseStatementNode();       } catch (IncompleteElementException e) {         configuration.addIncompleteStatement(statementParser);       }     }   } 

又看到了一个新的类 XMLStatementBuilder,来解析 sql 语句,当然 XMLStatementBuilder 同样继承于 BaseBuilder。

public class XMLStatementBuilder extends BaseBuilder {    private MapperBuilderAssistant builderAssistant;   private XNode context;   private String requiredDatabaseId; } 

上面是 XMLStatementBuilder 的属性。基本上与其他的 Builder 类似,主要看 parseStatementNode 方法:

public void parseStatementNode() {  //step1 比较 databaseId     String id = context.getStringAttribute("id");     String databaseId = context.getStringAttribute("databaseId");      if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {       return;     }   //step2 获取元素属性值     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);   //step3 引入sql片段     // Include Fragments before parsing     XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);     includeParser.applyIncludes(context.getNode());   // step4 处理 selectKey 的情况     // Parse selectKey after includes and remove them.     processSelectKeyNodes(id, parameterTypeClass, langDriver);   // step5 解析 sql 生成 SqlSource     // 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))           ? new Jdbc3KeyGenerator() : new NoKeyGenerator();     }      builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,         fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,         resultSetTypeEnum, flushCache, useCache, resultOrdered,          keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);   } 

代码比较多,分多个部分进行分析。

  1. 获取 databaseId 与 configuration 当中的 databaseId 进行比较。
  2. 获取元素的一些属性值。详细的属性说明, 请点击这里
  3. 引入 sql 片段,在解析之前
  4. 处理 selectKey 的情况
  5. 解析 sql 生成 SqlSource

XMLStatementBuilder.parseStatementNode 第三步

依次根据源码进行分析,第一步和第二步比较简单不详细分析,下面是 step3 的代码:

XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);     includeParser.applyIncludes(context.getNode()); 

在使用 select 或者 insert 等元素的时候,我们可以在里面引入 sql 节点,便于重复使用公用的一段sql 语句。在解析的时候我们先要把外部引入的 sql 组合到这个 statement 当中来。

public void applyIncludes(Node source) {     Properties variablesContext = new Properties();     Properties configurationVariables = configuration.getVariables();     if (configurationVariables != null) {       variablesContext.putAll(configurationVariables);     }     applyIncludes(source, variablesContext);   }  private void applyIncludes(Node source, final Properties variablesContext) {     if (source.getNodeName().equals("include")) {       // new full context for included SQL - contains inherited context and new variables from current include node       Properties fullContext;        String refid = getStringAttribute(source, "refid");       // replace variables in include refid value       refid = PropertyParser.parse(refid, variablesContext);       Node toInclude = findSqlFragment(refid);       Properties newVariablesContext = getVariablesContext(source, variablesContext);       if (!newVariablesContext.isEmpty()) {         // merge contexts         fullContext = new Properties();         fullContext.putAll(variablesContext);         fullContext.putAll(newVariablesContext);       } else {         // no new context - use inherited fully         fullContext = variablesContext;       }       applyIncludes(toInclude, fullContext);       if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {         toInclude = source.getOwnerDocument().importNode(toInclude, true);       }       source.getParentNode().replaceChild(toInclude, source);       while (toInclude.hasChildNodes()) {         toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);       }       toInclude.getParentNode().removeChild(toInclude);     } else if (source.getNodeType() == Node.ELEMENT_NODE) {       NodeList children = source.getChildNodes();       for (int i=0; i<children.getLength(); i++) {         applyIncludes(children.item(i), variablesContext);       }     } else if (source.getNodeType() == Node.ATTRIBUTE_NODE && !variablesContext.isEmpty()) {       // replace variables in all attribute values       source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));     } else if (source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) {       // replace variables ins all text nodes       source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));     }   } 

代码比较长,但是逻辑比较清晰。这是一个重载的方法,因为在 XMLStatementBuilder 里面调用的 时候传入的是原生的 node 节点,也就是说可能是 select、update、insert 或者 delete。

所以在下面私有的 applyIncludes 方法执行的时候,第一次执行的逻辑一定是下面这段逻辑:

 else if (source.getNodeType() == Node.ELEMENT_NODE) {       NodeList children = source.getChildNodes();       for (int i=0; i<children.getLength(); i++) {         applyIncludes(children.item(i), variablesContext);       }     } 

获取当前 node 节点的所有子节点,然后再次循环调用自己。下面分析 node name 是 include 的分支:

先获取到 include 的 refid 属性,然后判断是否是定义的 properties 赋值的,解析出来真正的 refid。

String refid = getStringAttribute(source, "refid");       // replace variables in include refid value refid = PropertyParser.parse(refid, variablesContext); 

然后找到那个 sql 片段,比较简单,就是从 map 当中获取:

Node toInclude = findSqlFragment(refid);  private Node findSqlFragment(String refid) {     refid = builderAssistant.applyCurrentNamespace(refid, true);     try {       XNode nodeToInclude = configuration.getSqlFragments().get(refid);       return nodeToInclude.getNode().cloneNode(true);     } catch (IllegalArgumentException e) {       throw new IncompleteElementException("Could not find SQL statement to include with refid '" + refid + "'", e);     }   } 

下一步是使用查询到 sql 的 node,继续调用此方法看是否有嵌套的 sql 引入。

applyIncludes(toInclude, fullContext); 

下一步是把 include 的 sql 节点,使用他的子节点替换掉(先把引入的 sql 子节点都插入到它的前面,然后在删除掉这个 sql 节点):

while (toInclude.hasChildNodes()) {         toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);       } toInclude.getParentNode().removeChild(toInclude); 

当然剩下的连个 if 分支都是对属性变量的替换操作。

else if (source.getNodeType() == Node.ATTRIBUTE_NODE && !variablesContext.isEmpty()) {       // replace variables in all attribute values       source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));     } else if (source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) {       // replace variables ins all text nodes       source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));     } 

下次继续分析解析的第四步和第五步。

—EOF—

原文  http://renchx.com/mybatis5
正文到此结束
Loading...