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); }
代码比较多,分多个部分进行分析。
- 获取 databaseId 与 configuration 当中的 databaseId 进行比较。
- 获取元素的一些属性值。详细的属性说明, 请点击这里
- 引入 sql 片段,在解析之前
- 处理 selectKey 的情况
- 解析 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—