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()); }
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解析的时候比较重要的抽象类