「Mybatis」动态SQL源码分析

前情概述 最近,在阅读源码,阅读到标签解析的时候发现,各家的实现方式不一样
例如dubbo,所有标签解析,全部在一个方法中,然后各种ifelse解析,只有在最底层,只存在一种标签的情况下,才用for循环解析,代码如下(不想看可以跳过这一部分)

private static BeanDefinition parse(Element element, ParserContext parserContext, Class beanClass, boolean required) { RootBeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClass(beanClass); beanDefinition.setLazyInit(false); String id = element.getAttribute("id"); if ((id == null || id.length() == 0) && required) { String generatedBeanName = element.getAttribute("name"); if (generatedBeanName == null || generatedBeanName.length() == 0) { if (ProtocolConfig.class.equals(beanClass)) { generatedBeanName = "dubbo"; } else { generatedBeanName = element.getAttribute("interface"); } } if (generatedBeanName == null || generatedBeanName.length() == 0) { generatedBeanName = beanClass.getName(); } id = generatedBeanName; int counter = 2; while(parserContext.getRegistry().containsBeanDefinition(id)) { id = generatedBeanName + (counter ++); } } if (id != null && id.length() > 0) { if (parserContext.getRegistry().containsBeanDefinition(id)){ throw new IllegalStateException("Duplicate spring bean id " + id); } parserContext.getRegistry().registerBeanDefinition(id, beanDefinition); beanDefinition.getPropertyValues().addPropertyValue("id", id); } if (ProtocolConfig.class.equals(beanClass)) { for (String name : parserContext.getRegistry().getBeanDefinitionNames()) { BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name); PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol"); if (property != null) { Object value = https://www.it610.com/article/property.getValue(); if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) { definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id)); } } } } else if (ServiceBean.class.equals(beanClass)) { String className = element.getAttribute("class"); if(className != null && className.length() > 0) { RootBeanDefinition classDefinition = new RootBeanDefinition(); classDefinition.setBeanClass(ReflectUtils.forName(className)); classDefinition.setLazyInit(false); parseProperties(element.getChildNodes(), classDefinition); beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl")); } } else if (ProviderConfig.class.equals(beanClass)) { parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition); } else if (ConsumerConfig.class.equals(beanClass)) { parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition); } Set props = new HashSet(); ManagedMap parameters = null; for (Method setter : beanClass.getMethods()) { String name = setter.getName(); if (name.length() > 3 && name.startsWith("set") && Modifier.isPublic(setter.getModifiers()) && setter.getParameterTypes().length == 1) { Class type = setter.getParameterTypes()[0]; String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), "-"); props.add(property); Method getter = null; try { getter = beanClass.getMethod("get" + name.substring(3), new Class[0]); } catch (NoSuchMethodException e) { try { getter = beanClass.getMethod("is" + name.substring(3), new Class[0]); } catch (NoSuchMethodException e2) { } } if (getter == null || ! Modifier.isPublic(getter.getModifiers()) || ! type.equals(getter.getReturnType())) { continue; } if ("parameters".equals(property)) { parameters = parseParameters(element.getChildNodes(), beanDefinition); } else if ("methods".equals(property)) { parseMethods(id, element.getChildNodes(), beanDefinition, parserContext); } else if ("arguments".equals(property)) { parseArguments(id, element.getChildNodes(), beanDefinition, parserContext); } else { String value = https://www.it610.com/article/element.getAttribute(property); if (value != null) { value = value.trim(); if (value.length()> 0) { if ("registry".equals(property) && RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(value)) { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress(RegistryConfig.NO_AVAILABLE); beanDefinition.getPropertyValues().addPropertyValue(property, registryConfig); } else if ("registry".equals(property) && value.indexOf(',') != -1) { parseMultiRef("registries", value, beanDefinition, parserContext); } else if ("provider".equals(property) && value.indexOf(',') != -1) { parseMultiRef("providers", value, beanDefinition, parserContext); } else if ("protocol".equals(property) && value.indexOf(',') != -1) { parseMultiRef("protocols", value, beanDefinition, parserContext); } else { Object reference; if (isPrimitive(type)) { if ("async".equals(property) && "false".equals(value) || "timeout".equals(property) && "0".equals(value) || "delay".equals(property) && "0".equals(value) || "version".equals(property) && "0.0.0".equals(value) || "stat".equals(property) && "-1".equals(value) || "reliable".equals(property) && "false".equals(value)) { // 兼容旧版本xsd中的default值 value = https://www.it610.com/article/null; } reference = value; } else if ("protocol".equals(property) && ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(value) && (! parserContext.getRegistry().containsBeanDefinition(value) || ! ProtocolConfig.class.getName().equals(parserContext.getRegistry().getBeanDefinition(value).getBeanClassName()))) { if ("dubbo:provider".equals(element.getTagName())) { logger.warn("Recommended replace to "); } // 兼容旧版本配置 ProtocolConfig protocol = new ProtocolConfig(); protocol.setName(value); reference = protocol; } else if ("monitor".equals(property) && (! parserContext.getRegistry().containsBeanDefinition(value) || ! MonitorConfig.class.getName().equals(parserContext.getRegistry().getBeanDefinition(value).getBeanClassName()))) { // 兼容旧版本配置 reference = convertMonitor(value); } else if ("onreturn".equals(property)) { int index = value.lastIndexOf("."); String returnRef = value.substring(0, index); String returnMethod = value.substring(index + 1); reference = new RuntimeBeanReference(returnRef); beanDefinition.getPropertyValues().addPropertyValue("onreturnMethod", returnMethod); } else if ("onthrow".equals(property)) { int index = value.lastIndexOf("."); String throwRef = value.substring(0, index); String throwMethod = value.substring(index + 1); reference = new RuntimeBeanReference(throwRef); beanDefinition.getPropertyValues().addPropertyValue("onthrowMethod", throwMethod); } else { if ("ref".equals(property) && parserContext.getRegistry().containsBeanDefinition(value)) { BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value); if (! refBean.isSingleton()) { throw new IllegalStateException("The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: "); } } reference = new RuntimeBeanReference(value); } beanDefinition.getPropertyValues().addPropertyValue(property, reference); } } } } } } ///略 return beanDefinition; }

【「Mybatis」动态SQL源码分析】突然联想到Mybatis的动态SQL,之前看的时候没有深究,他应该也是比dubbo的复杂的,但是纯暴力的方式,解决不了动态SQL,应该是递归的方式解决,就抱着这个疑问学习下
结果概览 先预览一下xml
select * from ${schema}.names where id = #{id} and true

再看一下Mybatis的解析后的结果
「Mybatis」动态SQL源码分析
文章图片

看到这个应该就相当清晰明了了,他按照动态SQL的结构,也封装成了对应的树状结构。
源码分析 解析标签
解析核心呢就是org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags,上面就是碰到是文本节点,添加进contents里面,下面就是调用对应的模版处理。
/** * 动态解析 * @param node 初始为根节点 * @return */ protected MixedSqlNode parseDynamicTags(XNode node) { List contents = new ArrayList<>(); //子节点 NodeList children = node.getNode().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { String data = https://www.it610.com/article/child.getStringBody(""); TextSqlNode textSqlNode = new TextSqlNode(data); if (textSqlNode.isDynamic()) { contents.add(textSqlNode); isDynamic = true; } else { contents.add(new StaticTextSqlNode(data)); } //标签节点 } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 String nodeName = child.getNode().getNodeName(); //对应各种handler处理器 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); }

各个处理模版
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()); }

对于内部还允许嵌套的标签,还会调用parseDynamicTags进行递归解析,最终解析的结果就是我贴的那张图啦
private interface NodeHandler { void handleNode(XNode nodeToHandle, List targetContents); } private class IfHandler implements NodeHandler { public IfHandler() { // Prevent Synthetic Access }@Override public void handleNode(XNode nodeToHandle, List targetContents) { MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); String test = nodeToHandle.getStringAttribute("test"); IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test); targetContents.add(ifSqlNode); } }

返回完整的sql 方法入口为org.apache.ibatis.scripting.xmltags.DynamicSqlSource#getBoundSql
@Override public BoundSql getBoundSql(Object parameterObject) { //创建DynamicContext DynamicContext context = new DynamicContext(configuration, parameterObject); //解析标签 rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); //处理#{} SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); context.getBindings().forEach(boundSql::setAdditionalParameter); return boundSql; }

还记得前面的树形结构吗,这次就是从根节点递归,同样也是递归解析,举个if的小例子。
public class IfSqlNode implements SqlNode { private final ExpressionEvaluator evaluator; private final String test; private final SqlNode contents; public IfSqlNode(SqlNode contents, String test) { this.test = test; this.contents = contents; this.evaluator = new ExpressionEvaluator(); }@Override public boolean apply(DynamicContext context) { if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false; }}

此时DynamicContext经历过递归后的解析结果如下
select * from ibtest.names where id = #{__frch_id_0} union select * from ibtest.names where id = #{__frch_id_1} union select * from ibtest.names where id = #{__frch_id_2} and WHERE true

但是jdbc提供的也只有'?'所以下面需要解析#{},最终解析效果如下,解析#{}就懒得贴了
select * from ibtest.names where id = ? union select * from ibtest.names where id = ? union select * from ibtest.names where id = ? and WHERE true

分析结束

    推荐阅读