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] =?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