From b1af0bcee46c492a96a0b7a5daff16c1e646cb09 Mon Sep 17 00:00:00 2001
From: FuriousPws002 <1938485828@qq.com>
Date: Tue, 16 Apr 2024 22:04:25 +0800
Subject: [PATCH 1/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0if=E6=A0=87=E7=AD=BE?=
=?UTF-8?q?=E5=92=8Ctrim=E6=A0=87=E7=AD=BE=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 5 ++
.../scripting/xmltags/DynamicContext.java | 13 +++
.../scripting/xmltags/DynamicSqlSource.java | 30 +++++++
.../ibatis/scripting/xmltags/IfSqlNode.java | 30 +++++++
.../ibatis/scripting/xmltags/TrimSqlNode.java | 89 +++++++++++++++++++
.../scripting/xmltags/XMLScriptBuilder.java | 51 +++++++++++
6 files changed, 218 insertions(+)
create mode 100644 src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java
create mode 100644 src/main/java/org/apache/ibatis/scripting/xmltags/IfSqlNode.java
create mode 100644 src/main/java/org/apache/ibatis/scripting/xmltags/TrimSqlNode.java
diff --git a/pom.xml b/pom.xml
index 1c8135f..87aaddb 100755
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,11 @@
commons-lang3
3.14.0
+
+ ognl
+ ognl
+ 3.4.2
+
diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java
index 15822b8..2e4eaf5 100644
--- a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java
+++ b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java
@@ -8,10 +8,15 @@
public class DynamicContext {
private final StringJoiner sqlBuilder = new StringJoiner(" ");
+ private Object parameterObject;
public DynamicContext() {
}
+ public DynamicContext(Object parameterObject) {
+ this.parameterObject = parameterObject;
+ }
+
public void appendSql(String sql) {
sqlBuilder.add(sql);
}
@@ -19,4 +24,12 @@ public void appendSql(String sql) {
public String getSql() {
return sqlBuilder.toString().trim();
}
+
+ public Object getParameterObject() {
+ return parameterObject;
+ }
+
+ public void setParameterObject(Object parameterObject) {
+ this.parameterObject = parameterObject;
+ }
}
diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java
new file mode 100644
index 0000000..dd3009b
--- /dev/null
+++ b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java
@@ -0,0 +1,30 @@
+package org.apache.ibatis.scripting.xmltags;
+
+import org.apache.ibatis.builder.SqlSourceBuilder;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.SqlSource;
+import org.apache.ibatis.session.Configuration;
+
+/**
+ * @author furious 2024/4/16
+ */
+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) {
+ DynamicContext context = new DynamicContext(parameterObject);
+ rootSqlNode.apply(context);
+ SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
+ Class> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
+ SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType);
+ return sqlSource.getBoundSql(parameterObject);
+ }
+}
diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/IfSqlNode.java b/src/main/java/org/apache/ibatis/scripting/xmltags/IfSqlNode.java
new file mode 100644
index 0000000..80a2415
--- /dev/null
+++ b/src/main/java/org/apache/ibatis/scripting/xmltags/IfSqlNode.java
@@ -0,0 +1,30 @@
+package org.apache.ibatis.scripting.xmltags;
+
+import ognl.Ognl;
+
+/**
+ * @author furious 2024/4/16
+ */
+public class IfSqlNode implements SqlNode {
+
+ private final String test;
+ private final SqlNode contents;
+
+ public IfSqlNode(String test, SqlNode contents) {
+ this.test = test;
+ this.contents = contents;
+ }
+
+ @Override
+ public boolean apply(DynamicContext context) {
+ try {
+ Object value = Ognl.getValue(Ognl.parseExpression(test), context.getParameterObject());
+ if (value instanceof Boolean && (Boolean) value) {
+ contents.apply(context);
+ }
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/TrimSqlNode.java b/src/main/java/org/apache/ibatis/scripting/xmltags/TrimSqlNode.java
new file mode 100644
index 0000000..1d057a4
--- /dev/null
+++ b/src/main/java/org/apache/ibatis/scripting/xmltags/TrimSqlNode.java
@@ -0,0 +1,89 @@
+package org.apache.ibatis.scripting.xmltags;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * @author furious 2024/4/16
+ */
+public class TrimSqlNode implements SqlNode {
+
+ private final SqlNode contents;
+ private final String prefix;
+ private final List prefixesToOverride;
+
+ public TrimSqlNode(SqlNode contents, String prefix, String prefixesToOverride) {
+ this.contents = contents;
+ this.prefix = prefix;
+ this.prefixesToOverride = parseOverrides(prefixesToOverride);
+ }
+
+ @Override
+ public boolean apply(DynamicContext context) {
+ FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
+ boolean result = contents.apply(filteredDynamicContext);
+ filteredDynamicContext.applyAll();
+ return result;
+ }
+
+ private static List parseOverrides(String overrides) {
+ if (StringUtils.isEmpty(overrides)) {
+ return Collections.emptyList();
+ }
+ return Arrays.stream(overrides.split("\\|")).collect(Collectors.toList());
+ }
+
+ private class FilteredDynamicContext extends DynamicContext {
+ private final DynamicContext delegate;
+ private StringBuilder sqlBuffer = new StringBuilder();
+
+ public FilteredDynamicContext(DynamicContext delegate) {
+ this.delegate = delegate;
+ }
+
+ public void applyAll() {
+ sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
+ String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase();
+ if (trimmedUppercaseSql.length() > 0) {
+ applyPrefix(sqlBuffer, trimmedUppercaseSql);
+ }
+ delegate.appendSql(sqlBuffer.toString());
+ }
+
+ @Override
+ public void appendSql(String sql) {
+ sqlBuffer.append(sql);
+ }
+
+ @Override
+ public String getSql() {
+ return delegate.getSql();
+ }
+
+ @Override
+ public Object getParameterObject() {
+ return delegate.getParameterObject();
+ }
+
+ private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
+ if (Objects.nonNull(prefixesToOverride)) {
+ for (String remove : prefixesToOverride) {
+ if (trimmedUppercaseSql.startsWith(remove)) {
+ sql.delete(0, remove.trim().length());
+ break;
+ }
+ }
+ }
+ if (prefix != null) {
+ sql.insert(0, " ");
+ sql.insert(0, prefix);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java b/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java
index b0a82c5..5646254 100644
--- a/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java
+++ b/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java
@@ -1,9 +1,12 @@
package org.apache.ibatis.scripting.xmltags;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
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;
@@ -18,6 +21,8 @@ public class XMLScriptBuilder extends BaseBuilder {
private final XNode context;
private final Class> parameterType;
+ private boolean isDynamic;
+ private final Map nodeHandlerMap = new HashMap<>();
public XMLScriptBuilder(Configuration configuration, XNode context) {
this(configuration, context, null);
@@ -27,10 +32,15 @@ public XMLScriptBuilder(Configuration configuration, XNode context, Class> par
super(configuration);
this.context = context;
this.parameterType = parameterType;
+ nodeHandlerMap.put("if", new IfHandler());
+ nodeHandlerMap.put("trim", new TrimHandler());
}
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
+ if (isDynamic) {
+ return new DynamicSqlSource(configuration, rootSqlNode);
+ }
return new RawSqlSource(configuration, rootSqlNode, parameterType);
}
@@ -41,8 +51,49 @@ protected MixedSqlNode parseDynamicTags(XNode node) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.TEXT_NODE) {
contents.add(new StaticTextSqlNode(child.getBody()));
+ } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) {
+ String nodeName = child.getNode().getNodeName();
+ NodeHandler handler = nodeHandlerMap.get(nodeName);
+ if (handler == null) {
+ throw new BuilderException("not support " + nodeName + " NodeHandler");
+ }
+ handler.handleNode(child, contents);
+ isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
+
+ private interface NodeHandler {
+ void handleNode(XNode nodeToHandle, List targetContents);
+ }
+
+ /**
+ * 处理 标签
+ */
+ private class IfHandler implements NodeHandler {
+
+ @Override
+ public void handleNode(XNode nodeToHandle, List targetContents) {
+ MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
+ String test = nodeToHandle.getAttribute("test");
+ IfSqlNode ifSqlNode = new IfSqlNode(test, mixedSqlNode);
+ targetContents.add(ifSqlNode);
+ }
+ }
+
+ /**
+ * 处理 标签
+ */
+ private class TrimHandler implements NodeHandler {
+
+ @Override
+ public void handleNode(XNode nodeToHandle, List targetContents) {
+ MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
+ String prefix = nodeToHandle.getAttribute("prefix");
+ String prefixOverrides = nodeToHandle.getAttribute("prefixOverrides");
+ TrimSqlNode trim = new TrimSqlNode(mixedSqlNode, prefix, prefixOverrides);
+ targetContents.add(trim);
+ }
+ }
}
From 083c167c69af61c23ac52b88a811d79982653567 Mon Sep 17 00:00:00 2001
From: FuriousPws002 <1938485828@qq.com>
Date: Wed, 17 Apr 2024 21:37:04 +0800
Subject: [PATCH 2/2] =?UTF-8?q?foreach=E6=A0=87=E7=AD=BE=E5=AE=9E=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 1 +
.../org/apache/ibatis/mapping/BoundSql.java | 24 +++
.../ibatis/mapping/MappedStatement.java | 2 +-
.../defaults/DefaultParameterHandler.java | 4 +-
.../scripting/xmltags/DynamicContext.java | 11 ++
.../scripting/xmltags/DynamicSqlSource.java | 4 +-
.../scripting/xmltags/ForeachSqlNode.java | 157 ++++++++++++++++++
.../scripting/xmltags/XMLScriptBuilder.java | 17 ++
.../org/apache/ibatis/dao/UserMapper.java | 4 +
.../apache/ibatis/session/SqlSessionTest.java | 39 +++++
src/test/resources/mapper/UserMapper.xml | 24 +++
11 files changed, 284 insertions(+), 3 deletions(-)
create mode 100644 src/main/java/org/apache/ibatis/scripting/xmltags/ForeachSqlNode.java
diff --git a/README.md b/README.md
index ad6b791..0912090 100644
--- a/README.md
+++ b/README.md
@@ -7,3 +7,4 @@
[4.参数绑定](https://github.com/FuriousPws002/mini-mybatis/wiki/4.%E5%8F%82%E6%95%B0%E7%BB%91%E5%AE%9A "Markdown")
[5.resultType结果集处理](https://github.com/FuriousPws002/mini-mybatis/wiki/5.resultType%E7%BB%93%E6%9E%9C%E9%9B%86%E5%A4%84%E7%90%86 "Markdown")
[6.resultMap结果集处理](https://github.com/FuriousPws002/mini-mybatis/wiki/6.resultMap%E7%BB%93%E6%9E%9C%E9%9B%86%E5%A4%84%E7%90%86 "Markdown")
+[7.动态sql](https://github.com/FuriousPws002/mini-mybatis/wiki/7.%E5%8A%A8%E6%80%81sql "Markdown")
diff --git a/src/main/java/org/apache/ibatis/mapping/BoundSql.java b/src/main/java/org/apache/ibatis/mapping/BoundSql.java
index 83ac02c..ca1c1ed 100644
--- a/src/main/java/org/apache/ibatis/mapping/BoundSql.java
+++ b/src/main/java/org/apache/ibatis/mapping/BoundSql.java
@@ -1,6 +1,8 @@
package org.apache.ibatis.mapping;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* @author furious 2024/4/7
@@ -10,15 +12,21 @@ public class BoundSql {
private final String sql;
private final List parameterMappings;
private final Object parameterObject;
+ private final Map additionalParameters;
public BoundSql(String sql) {
this(sql, null, null);
}
public BoundSql(String sql, List parameterMappings, Object parameterObject) {
+ this(sql, parameterMappings, parameterObject, new HashMap<>());
+ }
+
+ public BoundSql(String sql, List parameterMappings, Object parameterObject, Map additionalParameters) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
+ this.additionalParameters = additionalParameters;
}
public List getParameterMappings() {
@@ -32,4 +40,20 @@ public Object getParameterObject() {
public String getSql() {
return this.sql;
}
+
+ public void setAdditionalParameter(String key, Object value) {
+ additionalParameters.put(key, value);
+ }
+
+ public Object getAdditionalParameter(String key) {
+ return additionalParameters.get(key);
+ }
+
+ public boolean hasAdditionalParameter(String key) {
+ return additionalParameters.containsKey(key);
+ }
+
+ public Map getAdditionalParameters() {
+ return additionalParameters;
+ }
}
diff --git a/src/main/java/org/apache/ibatis/mapping/MappedStatement.java b/src/main/java/org/apache/ibatis/mapping/MappedStatement.java
index ff92d84..20c37b4 100644
--- a/src/main/java/org/apache/ibatis/mapping/MappedStatement.java
+++ b/src/main/java/org/apache/ibatis/mapping/MappedStatement.java
@@ -28,7 +28,7 @@ public Configuration getConfiguration() {
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
- return new BoundSql(boundSql.getSql(),boundSql.getParameterMappings(),parameterObject);
+ return new BoundSql(boundSql.getSql(), boundSql.getParameterMappings(), parameterObject, boundSql.getAdditionalParameters());
}
public ResultMap getResultMap() {
diff --git a/src/main/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandler.java b/src/main/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandler.java
index 952dade..b3421a0 100644
--- a/src/main/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandler.java
+++ b/src/main/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandler.java
@@ -47,7 +47,9 @@ public void setParameters(PreparedStatement ps) throws SQLException {
ParameterMapping parameterMapping = parameterMappings.get(i);
Object value;
String propertyName = parameterMapping.getProperty();
- if (Objects.isNull(parameterObject)) {
+ if (boundSql.hasAdditionalParameter(propertyName)) {
+ value = boundSql.getAdditionalParameter(propertyName);
+ }else if (Objects.isNull(parameterObject)) {
value = null;
} else if (configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java
index 2e4eaf5..76970c4 100644
--- a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java
+++ b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java
@@ -1,5 +1,7 @@
package org.apache.ibatis.scripting.xmltags;
+import java.util.HashMap;
+import java.util.Map;
import java.util.StringJoiner;
/**
@@ -9,6 +11,7 @@ public class DynamicContext {
private final StringJoiner sqlBuilder = new StringJoiner(" ");
private Object parameterObject;
+ private final Map bindings = new HashMap<>();
public DynamicContext() {
}
@@ -32,4 +35,12 @@ public Object getParameterObject() {
public void setParameterObject(Object parameterObject) {
this.parameterObject = parameterObject;
}
+
+ public Map getBindings() {
+ return bindings;
+ }
+
+ public void bind(String key, Object value) {
+ bindings.put(key, value);
+ }
}
diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java
index dd3009b..73041c6 100644
--- a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java
+++ b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java
@@ -25,6 +25,8 @@ public BoundSql getBoundSql(Object parameterObject) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType);
- return sqlSource.getBoundSql(parameterObject);
+ BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
+ context.getBindings().forEach(boundSql::setAdditionalParameter);
+ return boundSql;
}
}
diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/ForeachSqlNode.java b/src/main/java/org/apache/ibatis/scripting/xmltags/ForeachSqlNode.java
new file mode 100644
index 0000000..64447cf
--- /dev/null
+++ b/src/main/java/org/apache/ibatis/scripting/xmltags/ForeachSqlNode.java
@@ -0,0 +1,157 @@
+package org.apache.ibatis.scripting.xmltags;
+
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ibatis.parsing.GenericTokenParser;
+
+import ognl.Ognl;
+
+/**
+ * @author furious 2024/4/17
+ */
+public class ForeachSqlNode implements SqlNode {
+
+ public static final String ITEM_PREFIX = ForeachSqlNode.class.getName();
+
+ private final SqlNode contents;
+ private final String collection;
+ private final String item;
+ private final String open;
+ private final String close;
+ private final String separator;
+
+ public ForeachSqlNode(SqlNode contents, String collection, String item, String open, String close, String separator) {
+ this.contents = contents;
+ this.collection = collection;
+ this.item = item;
+ this.open = open;
+ this.close = close;
+ this.separator = separator;
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ public boolean apply(DynamicContext context) {
+ Iterable> iterable = null;
+ try {
+ Object value = Ognl.getValue(Ognl.parseExpression(collection), context.getParameterObject());
+ if (value instanceof Iterable && ((Iterable) value).iterator().hasNext()) {
+ iterable = (Iterable) value;
+ }
+ } catch (Exception e) {
+ return false;
+ }
+ if (Objects.isNull(iterable)) {
+ return true;
+ }
+
+ applyOpen(context);
+ int index = 0;
+ for (Object o : iterable) {
+ DynamicContext oldContext = context;
+ if (index == 0) {
+ context = new SeparatorContext(context, "");
+ } else {
+ context = new SeparatorContext(context, separator);
+ }
+ applyItem(context, o, index);
+ contents.apply(new FilteredDynamicContext(context, item, index));
+ context = oldContext;
+ index++;
+ }
+ applyClose(context);
+ return true;
+ }
+
+ private void applyOpen(DynamicContext context) {
+ if (Objects.nonNull(open)) {
+ context.appendSql(open);
+ }
+ }
+
+ private void applyItem(DynamicContext context, Object o, int i) {
+ if (Objects.nonNull(item)) {
+ context.bind(item, o);
+ context.bind(itemizeItem(item, i), o);
+ }
+ }
+
+ private void applyClose(DynamicContext context) {
+ if (Objects.nonNull(close)) {
+ context.appendSql(close);
+ }
+ }
+
+ private static String itemizeItem(String item, int i) {
+ return ITEM_PREFIX + item + "_" + i;
+ }
+
+ private static class SeparatorContext extends DynamicContext {
+
+ private final DynamicContext delegate;
+ private final String separator;
+
+ public SeparatorContext(DynamicContext delegate, String separator) {
+ this.delegate = delegate;
+ this.separator = separator;
+ }
+
+ @Override
+ public Map getBindings() {
+ return delegate.getBindings();
+ }
+
+ @Override
+ public void bind(String name, Object value) {
+ delegate.bind(name, value);
+ }
+
+ @Override
+ public void appendSql(String sql) {
+ if (StringUtils.isNoneBlank(sql)) {
+ delegate.appendSql(separator);
+ }
+ delegate.appendSql(sql);
+ }
+
+ @Override
+ public String getSql() {
+ return delegate.getSql();
+ }
+ }
+
+ private static class FilteredDynamicContext extends DynamicContext {
+ private final DynamicContext delegate;
+ private final String item;
+ private final int index;
+
+ public FilteredDynamicContext(DynamicContext delegate, String item, int index) {
+ this.delegate = delegate;
+ this.item = item;
+ this.index = index;
+ }
+
+ @Override
+ public Map getBindings() {
+ return delegate.getBindings();
+ }
+
+ @Override
+ public void bind(String name, Object value) {
+ delegate.bind(name, value);
+ }
+
+ @Override
+ public String getSql() {
+ return delegate.getSql();
+ }
+
+ @Override
+ public void appendSql(String sql) {
+ GenericTokenParser parser = new GenericTokenParser("#{", "}", content -> "#{" + itemizeItem(item, index) + "}");
+ delegate.appendSql(parser.parse(sql));
+ }
+ }
+}
diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java b/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java
index 5646254..34ecc49 100644
--- a/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java
+++ b/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java
@@ -34,6 +34,7 @@ public XMLScriptBuilder(Configuration configuration, XNode context, Class> par
this.parameterType = parameterType;
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("trim", new TrimHandler());
+ nodeHandlerMap.put("foreach", new ForeachHandler());
}
public SqlSource parseScriptNode() {
@@ -96,4 +97,20 @@ public void handleNode(XNode nodeToHandle, List targetContents) {
targetContents.add(trim);
}
}
+
+ private class ForeachHandler implements NodeHandler {
+
+ @Override
+ public void handleNode(XNode nodeToHandle, List targetContents) {
+ MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
+ String collection = nodeToHandle.getAttribute("collection");
+ String item = nodeToHandle.getAttribute("item");
+ String open = nodeToHandle.getAttribute("open");
+ String close = nodeToHandle.getAttribute("close");
+ String separator = nodeToHandle.getAttribute("separator");
+ ForeachSqlNode forEachSqlNode = new ForeachSqlNode(mixedSqlNode, collection, item, open, close, separator);
+ targetContents.add(forEachSqlNode);
+ }
+ }
+
}
diff --git a/src/test/java/org/apache/ibatis/dao/UserMapper.java b/src/test/java/org/apache/ibatis/dao/UserMapper.java
index 5d9a47c..570e715 100644
--- a/src/test/java/org/apache/ibatis/dao/UserMapper.java
+++ b/src/test/java/org/apache/ibatis/dao/UserMapper.java
@@ -35,4 +35,8 @@ public interface UserMapper {
List selectResultMap();
List selectNestedResultMap();
+
+ List listDynamic(@Param("name") String name, @Param("age") Integer age);
+
+ List listForeach(@Param("ids") List ids);
}
diff --git a/src/test/java/org/apache/ibatis/session/SqlSessionTest.java b/src/test/java/org/apache/ibatis/session/SqlSessionTest.java
index b269bed..d14dd7d 100644
--- a/src/test/java/org/apache/ibatis/session/SqlSessionTest.java
+++ b/src/test/java/org/apache/ibatis/session/SqlSessionTest.java
@@ -1,5 +1,6 @@
package org.apache.ibatis.session;
+import java.util.ArrayList;
import java.util.List;
import org.apache.ibatis.DataSourceBuilderTest;
@@ -127,4 +128,42 @@ public void queryNestedResultMap() {
Assert.assertNotNull(user.getName());
Assert.assertNotNull(user.getCarList());
}
+
+ /**
+ * 动态SQL-包含trim标签和if标签
+ */
+ @Test
+ public void queryDynamicSql() {
+ UserMapper userMapper = getUserMapper();
+ List list1 = userMapper.listDynamic(null, null);
+ Assert.assertNotNull(list1);
+ List list2 = userMapper.listDynamic("Sam", null);
+ Assert.assertNotNull(list2);
+ List list3 = userMapper.listDynamic(null, 20);
+ Assert.assertNotNull(list3);
+ List list4 = userMapper.listDynamic("Sam", 20);
+ Assert.assertNotNull(list4);
+ }
+
+ /**
+ * foreach标签测试
+ */
+ @Test
+ public void queryForeachTag() {
+ UserMapper userMapper = getUserMapper();
+ List idList = new ArrayList<>();
+ idList.add(1L);
+ idList.add(2L);
+ List list = userMapper.listForeach(idList);
+ Assert.assertNotNull(list);
+ }
+
+ private UserMapper getUserMapper() {
+ Configuration configuration = new Configuration();
+ configuration.setDataSource(DataSourceBuilderTest.build());
+ configuration.addMapper(UserMapper.class);
+ SqlSession sqlSession = new DefaultSqlSession(configuration);
+ return sqlSession.getMapper(UserMapper.class);
+ }
+
}
diff --git a/src/test/resources/mapper/UserMapper.xml b/src/test/resources/mapper/UserMapper.xml
index 5b7b2a4..f9909db 100644
--- a/src/test/resources/mapper/UserMapper.xml
+++ b/src/test/resources/mapper/UserMapper.xml
@@ -53,6 +53,30 @@
LEFT JOIN car ON relation.car_id = car.id
+
+
\ No newline at end of file