Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory footprint optimization and performance improvement #3118

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
21 changes: 21 additions & 0 deletions src/main/java/org/apache/ibatis/parsing/XNode.java
Expand Up @@ -359,4 +359,25 @@ private String getBodyData(Node child) {
return null;
}

/**
* Builds text representation of this node, which can be persisted as key and maps the SQLNode.
* So that, subsequent references of this Xnode will avoid building SQLNodes
* @return string
*/
public String toStringWithContent() {
StringBuilder builder = new StringBuilder();
toStringWithContent(builder);
return builder.toString();
}

private void toStringWithContent(StringBuilder builder) {
builder.append(toString());
Node clonedNode = getNode().cloneNode(true);
builder.append("<textContent>");
builder.append(clonedNode.getTextContent());
builder.append("<textContent>");
builder.append("<body>");
builder.append(body);
builder.append("<body>");
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2009-2023 the original author or authors.
* Copyright 2009-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,7 +15,11 @@
*/
package org.apache.ibatis.scripting.xmltags;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -59,7 +63,7 @@ private void initNodeHandlerMap() {
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
nodeHandlerMap.put("bind", new BindHandler(configuration));
}

public SqlSource parseScriptNode() {
Expand All @@ -79,13 +83,27 @@ protected MixedSqlNode parseDynamicTags(XNode node) {
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 = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
String key = getHash(child.getStringBody(""));
if (configuration.containsSqlNode(key)) {
SqlNode sqlNode = configuration.getSqlNode(key);
if (sqlNode instanceof TextSqlNode) {
isDynamic = true;
}
contents.add(sqlNode);
} else {
contents.add(new StaticTextSqlNode(data));
String data = child.getStringBody("").replaceAll("\t", " ").replaceAll("\n", " ");
if (data.trim().length() > 0) {
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
configuration.addSqlNode(key, textSqlNode);
} else {
StaticTextSqlNode staticTextSqlNode = new StaticTextSqlNode(data);
contents.add(staticTextSqlNode);
configuration.addSqlNode(key, staticTextSqlNode);
}
}
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
Expand All @@ -100,21 +118,50 @@ protected MixedSqlNode parseDynamicTags(XNode node) {
return new MixedSqlNode(contents);
}

private MixedSqlNode getMixedNode(XNode nodeToHandle) {
String key = getHash(nodeToHandle.toStringWithContent());
if (configuration.containsSqlNode(key)) {
return (MixedSqlNode) configuration.getSqlNode(key);
} else {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
configuration.addSqlNode(key, mixedSqlNode);
return mixedSqlNode;
}
}

private static String getHash(String key) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
byte hashBytes[] = messageDigest.digest(key.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hashBytes);
} catch (NoSuchAlgorithmException e) {
return key;
}
}

private interface NodeHandler {
void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}

private static class BindHandler implements NodeHandler {
public BindHandler() {
// Prevent Synthetic Access
private final Configuration configuration;

public BindHandler(Configuration configuration) {
this.configuration = configuration;
}

@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);
String key = getHash(nodeToHandle.toStringWithContent());
if (configuration.containsSqlNode(key)) {
targetContents.add(configuration.getSqlNode(key));
} else {
VarDeclSqlNode sqlNode = new VarDeclSqlNode(name, expression);
configuration.addSqlNode(key, sqlNode);
targetContents.add(sqlNode);
}
}
}

Expand All @@ -125,13 +172,13 @@ public TrimHandler() {

@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = getMixedNode(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);
targetContents
.add(new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides));
}
}

Expand All @@ -142,9 +189,8 @@ public WhereHandler() {

@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
targetContents.add(where);
MixedSqlNode mixedSqlNode = getMixedNode(nodeToHandle);
targetContents.add(new WhereSqlNode(configuration, mixedSqlNode));
}
}

Expand All @@ -155,9 +201,8 @@ public SetHandler() {

@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
targetContents.add(set);
MixedSqlNode mixedSqlNode = getMixedNode(nodeToHandle);
targetContents.add(new SetSqlNode(configuration, mixedSqlNode));
}
}

Expand All @@ -168,17 +213,16 @@ public ForEachHandler() {

@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = getMixedNode(nodeToHandle);
String collection = nodeToHandle.getStringAttribute("collection");
Boolean nullable = nodeToHandle.getBooleanAttribute("nullable");
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, nullable, index, item,
open, close, separator);
targetContents.add(forEachSqlNode);
targetContents.add(
new ForEachSqlNode(configuration, mixedSqlNode, collection, nullable, index, item, open, close, separator));
}
}

Expand All @@ -189,10 +233,9 @@ public IfHandler() {

@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = getMixedNode(nodeToHandle);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
targetContents.add(new IfSqlNode(mixedSqlNode, test));
}
}

Expand All @@ -203,8 +246,14 @@ public OtherwiseHandler() {

@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
targetContents.add(mixedSqlNode);
String key = getHash(nodeToHandle.toStringWithContent());
if (configuration.containsSqlNode(key)) {
targetContents.add(configuration.getSqlNode(key));
} else {
SqlNode sqlNode = parseDynamicTags(nodeToHandle);
configuration.addSqlNode(key, sqlNode);
targetContents.add(sqlNode);
}
}
}

Expand All @@ -219,8 +268,7 @@ public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> otherwiseSqlNodes = new ArrayList<>();
handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
targetContents.add(chooseSqlNode);
targetContents.add(new ChooseSqlNode(whenSqlNodes, defaultSqlNode));
}

private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes,
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/apache/ibatis/session/Configuration.java
Expand Up @@ -88,6 +88,7 @@
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.scripting.LanguageDriverRegistry;
import org.apache.ibatis.scripting.defaults.RawLanguageDriver;
import org.apache.ibatis.scripting.xmltags.SqlNode;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
Expand Down Expand Up @@ -161,6 +162,7 @@ public class Configuration {
.conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and "
+ targetValue.getResource());
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
protected final Map<String, SqlNode> sqlNodes = new HashMap<>();
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
Expand Down Expand Up @@ -832,6 +834,18 @@ public Collection<String> getMappedStatementNames() {
return mappedStatements.keySet();
}

public void addSqlNode(String key, SqlNode sqlNode) {
sqlNodes.put(key, sqlNode);
}

public boolean containsSqlNode(String key) {
return sqlNodes.containsKey(key);
}

public SqlNode getSqlNode(String key) {
return sqlNodes.get(key);
}

public Collection<MappedStatement> getMappedStatements() {
buildAllStatements();
return mappedStatements.values();
Expand Down