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