Переглянути джерело

Merge pull request #5487 from nieqiurong/fix_20230720093902

减少MappedStatement堆内存占用.
qmdx 2 роки тому
батько
коміт
c2381feba0

+ 7 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisXMLLanguageDriver.java

@@ -25,6 +25,7 @@ import org.apache.ibatis.executor.parameter.ParameterHandler;
 import org.apache.ibatis.mapping.BoundSql;
 import org.apache.ibatis.mapping.BoundSql;
 import org.apache.ibatis.mapping.MappedStatement;
 import org.apache.ibatis.mapping.MappedStatement;
 import org.apache.ibatis.mapping.SqlSource;
 import org.apache.ibatis.mapping.SqlSource;
+import org.apache.ibatis.parsing.XNode;
 import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
 import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
 import org.apache.ibatis.session.Configuration;
 import org.apache.ibatis.session.Configuration;
 
 
@@ -45,6 +46,12 @@ public class MybatisXMLLanguageDriver extends XMLLanguageDriver {
         return new MybatisParameterHandler(mappedStatement, parameterObject, boundSql);
         return new MybatisParameterHandler(mappedStatement, parameterObject, boundSql);
     }
     }
 
 
+    @Override
+    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
+        MybatisXMLScriptBuilder builder = new MybatisXMLScriptBuilder(configuration, script, parameterType);
+        return builder.parseScriptNode();
+    }
+
     @Override
     @Override
     public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
     public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
         GlobalConfig.DbConfig config = GlobalConfigUtils.getDbConfig(configuration);
         GlobalConfig.DbConfig config = GlobalConfigUtils.getDbConfig(configuration);

+ 308 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisXMLScriptBuilder.java

@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 2011-2022, 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 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
+ */
+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;
+        }
+        return CACHE_STRING.computeIfAbsent(str, WeakReference::new).get();
+    }
+
+
+    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);
+    }
+
+    static class WrapperXnode {
+
+        private final XNode xNode;
+
+        WrapperXnode(XNode xNode) {
+            this.xNode = xNode;
+        }
+
+        public String getStringBody(String def) {
+            return xNode.getStringBody(def);
+        }
+
+    }
+
+    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;
+        }
+    }
+
+}