|
@@ -0,0 +1,300 @@
|
|
|
+/*
|
|
|
+ * Copyright (c) 2011-2023, baomidou (jobob@qq.com).
|
|
|
+ *
|
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+ * you may not use this file except in compliance with the License.
|
|
|
+ * You may obtain a copy of the License at
|
|
|
+ *
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ *
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+ * See the License for the specific language governing permissions and
|
|
|
+ * limitations under the License.
|
|
|
+ */
|
|
|
+package com.baomidou.mybatisplus.core;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
|
|
+import org.apache.ibatis.builder.BaseBuilder;
|
|
|
+import org.apache.ibatis.builder.BuilderException;
|
|
|
+import org.apache.ibatis.mapping.SqlSource;
|
|
|
+import org.apache.ibatis.parsing.XNode;
|
|
|
+import org.apache.ibatis.scripting.defaults.RawSqlSource;
|
|
|
+import org.apache.ibatis.scripting.xmltags.ChooseSqlNode;
|
|
|
+import org.apache.ibatis.scripting.xmltags.DynamicSqlSource;
|
|
|
+import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
|
|
|
+import org.apache.ibatis.scripting.xmltags.IfSqlNode;
|
|
|
+import org.apache.ibatis.scripting.xmltags.MixedSqlNode;
|
|
|
+import org.apache.ibatis.scripting.xmltags.SetSqlNode;
|
|
|
+import org.apache.ibatis.scripting.xmltags.SqlNode;
|
|
|
+import org.apache.ibatis.scripting.xmltags.StaticTextSqlNode;
|
|
|
+import org.apache.ibatis.scripting.xmltags.TextSqlNode;
|
|
|
+import org.apache.ibatis.scripting.xmltags.TrimSqlNode;
|
|
|
+import org.apache.ibatis.scripting.xmltags.VarDeclSqlNode;
|
|
|
+import org.apache.ibatis.scripting.xmltags.WhereSqlNode;
|
|
|
+import org.apache.ibatis.scripting.xmltags.XMLScriptBuilder;
|
|
|
+import org.apache.ibatis.session.Configuration;
|
|
|
+import org.w3c.dom.Node;
|
|
|
+import org.w3c.dom.NodeList;
|
|
|
+
|
|
|
+import java.lang.ref.WeakReference;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.WeakHashMap;
|
|
|
+
|
|
|
+/**
|
|
|
+ * <p>试验性功能,解决mybatis堆内存过大的问题(看后期mybatis官方会不会解决堆内存占用问题)</p>
|
|
|
+ * <p>由于大量重复sql节点,导致堆内存过大(本质上属于string导致的堆内存增大问题)</p>
|
|
|
+ * <p>例如: {@code <if test="createTime!=null">create_time=#{createTime}</if>}等公共字段</p>
|
|
|
+ * <p>
|
|
|
+ * 解决方案: 将生成的xml节点值写入字符串常量池,减少后面重复字符串导致的问题
|
|
|
+ * <li>
|
|
|
+ * 方案一: 缓存一些特定的mybatis-plus生成的占位符与表达式和项目公共字段(改动有点多,需要增加一些特定xml属性来标记是mybatis-plus生成的节点,减少堆内存较少)
|
|
|
+ * </li>
|
|
|
+ * <li>
|
|
|
+ * 方案二: 直接将节点内容intern写入至字符串常量池(改动少,减少堆内存多,弊端可能会将一些无重复的字符串写入至常量池)
|
|
|
+ * </li>
|
|
|
+ * <li>
|
|
|
+ * 方案三: 模拟字符串常量池,减少重复字符串写入至堆内存(代码相对来说不好看点)
|
|
|
+ * </li>
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @author nieqiurong
|
|
|
+ * @see XMLScriptBuilder
|
|
|
+ * @since 3.5.3.2
|
|
|
+ */
|
|
|
+public class MybatisXMLScriptBuilder extends BaseBuilder {
|
|
|
+
|
|
|
+ private final XNode context;
|
|
|
+ private boolean isDynamic;
|
|
|
+ private final Class<?> parameterType;
|
|
|
+ private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();
|
|
|
+ private static final Map<String, WeakReference<String>> CACHE_STRING = new WeakHashMap<>();
|
|
|
+
|
|
|
+ public MybatisXMLScriptBuilder(Configuration configuration, XNode context) {
|
|
|
+ this(configuration, context, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ public MybatisXMLScriptBuilder(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());
|
|
|
+ }
|
|
|
+
|
|
|
+ public SqlSource parseScriptNode() {
|
|
|
+ MixedSqlNode rootSqlNode = parseDynamicTags(context);
|
|
|
+ SqlSource sqlSource;
|
|
|
+ if (isDynamic) {
|
|
|
+ sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
|
|
|
+ } else {
|
|
|
+ sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
|
|
|
+ }
|
|
|
+ return sqlSource;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 也可以将XNode节点包裹增强一下,来减少方法的引用,但需要创建对象,这里就直接将每个地方手动改一下了.
|
|
|
+ */
|
|
|
+ private synchronized static String cacheStr(String str) {
|
|
|
+ if (str == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String value = CACHE_STRING.computeIfAbsent(str, WeakReference::new).get();
|
|
|
+ //增强安全处理一下,如果实在是GC处理掉了(可能性小),就返回原来.
|
|
|
+ if (StringUtils.isNotBlank(value)) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ return str;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ protected MixedSqlNode parseDynamicTags(XNode node) {
|
|
|
+ List<SqlNode> 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 text = cacheStr(child.getStringBody(""));
|
|
|
+ TextSqlNode textSqlNode = new TextSqlNode(text);
|
|
|
+ if (textSqlNode.isDynamic()) {
|
|
|
+ contents.add(textSqlNode);
|
|
|
+ isDynamic = true;
|
|
|
+ } else {
|
|
|
+ contents.add(new StaticTextSqlNode(text));
|
|
|
+ }
|
|
|
+ } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 = cacheStr(nodeToHandle.getStringAttribute("name"));
|
|
|
+ final String expression = cacheStr(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 = cacheStr(nodeToHandle.getStringAttribute("prefix"));
|
|
|
+ String prefixOverrides = cacheStr(nodeToHandle.getStringAttribute("prefixOverrides"));
|
|
|
+ String suffix = cacheStr(nodeToHandle.getStringAttribute("suffix"));
|
|
|
+ String suffixOverrides = cacheStr(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) {
|
|
|
+ MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
|
|
|
+ String collection = cacheStr(nodeToHandle.getStringAttribute("collection"));
|
|
|
+ Boolean nullable = nodeToHandle.getBooleanAttribute("nullable");
|
|
|
+ String item = cacheStr(nodeToHandle.getStringAttribute("item"));
|
|
|
+ String index = cacheStr(nodeToHandle.getStringAttribute("index"));
|
|
|
+ String open = cacheStr(nodeToHandle.getStringAttribute("open"));
|
|
|
+ String close = cacheStr(nodeToHandle.getStringAttribute("close"));
|
|
|
+ String separator = cacheStr(nodeToHandle.getStringAttribute("separator"));
|
|
|
+ ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, nullable, 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) {
|
|
|
+ MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
|
|
|
+ String test = cacheStr(nodeToHandle.getStringAttribute("test"));
|
|
|
+ IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
|
|
|
+ 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<>();
|
|
|
+ List<SqlNode> otherwiseSqlNodes = new ArrayList<>();
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|