MyBatis源码分析(二、续)SqlSource创建流程,SQL如何解析?如何将#{id}变成?的
创始人
2025-05-30 00:21:08

文章目录

  • 实例
  • 一、SqlSource处理入口
  • 二、SqlSource处理逻辑
    • 1、XMLScriptBuilder 构造方法
    • 2、解析动态sql
    • 3、DynamicSqlSource
    • 4、RawSqlSource解析sql
      • (1)parse方法解析sql
  • 写在后面

实例

此处我们分析的sql:


一、SqlSource处理入口

在处理配置文件时,会处理Mapper.xml文件:

// org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {try {// issue #117 read properties firstpropertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(settings);typeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers")); // 处理mapper标签} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}
}// org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);try(InputStream inputStream = Resources.getResourceAsStream(resource)) {XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();}} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);try(InputStream inputStream = Resources.getUrlAsStream(url)){XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse(); // 解析Mapper.xml}} else if (resource == null && url == null && mapperClass != null) {Class mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}
}// org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {if (!configuration.isResourceLoaded(resource)) {configurationElement(parser.evalNode("/mapper"));configuration.addLoadedResource(resource);bindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();// 预处理StatementparsePendingStatements();
}// org.apache.ibatis.builder.xml.XMLMapperBuilder#parsePendingStatements
private void parsePendingStatements() {Collection incompleteStatements = configuration.getIncompleteStatements();synchronized (incompleteStatements) {Iterator iter = incompleteStatements.iterator();while (iter.hasNext()) {try {iter.next().parseStatementNode(); // 处理nodeiter.remove();} catch (IncompleteElementException e) {// Statement is still missing a resource...}}}
}
// org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
public void parseStatementNode() {String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}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 Fragments before parsingXMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());String parameterType = context.getStringAttribute("parameterType");Class parameterTypeClass = resolveClass(parameterType);String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);// Parse selectKey after includes and remove them.processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre:  and  were parsed and removed)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;}// 创建SqlSource核心逻辑SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String resultType = context.getStringAttribute("resultType");Class resultTypeClass = resolveClass(resultType);String resultMap = context.getStringAttribute("resultMap");String resultSetType = context.getStringAttribute("resultSetType");ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);if (resultSetTypeEnum == null) {resultSetTypeEnum = configuration.getDefaultResultSetType();}String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");String resultSets = context.getStringAttribute("resultSets");builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

我们找到了处理SqlSource的核心入口。

二、SqlSource处理逻辑

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

// org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class)
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) {// 初始化了动态SQL标签处理器XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);// 解析动态SQLreturn builder.parseScriptNode();
}

1、XMLScriptBuilder 构造方法

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

2、解析动态sql

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;if (isDynamic) {// 如果SQL中包含${}和动态SQL语句,则将SqlNode封装到DynamicSqlSource// 最终结果是:select id from blog where id = ${id}sqlSource = new DynamicSqlSource(configuration, rootSqlNode);} else {// 如果SQL中包含#{},则将SqlNode封装到RawSqlSource中,并指定parameterType// 最终的结果是:select id from blog where id = ?sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);}return sqlSource;
}

parseDynamicTags解析sql语句:

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

// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags
protected MixedSqlNode parseDynamicTags(XNode node) {List contents = new ArrayList<>();//获取