Skip to content

Commit

Permalink
foreach标签实现
Browse files Browse the repository at this point in the history
  • Loading branch information
FuriousPws002 committed Apr 17, 2024
1 parent b1af0bc commit 083c167
Show file tree
Hide file tree
Showing 11 changed files with 284 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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") <br>
[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") <br>
[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") <br>
[7.动态sql](https://github.com/FuriousPws002/mini-mybatis/wiki/7.%E5%8A%A8%E6%80%81sql "Markdown") <br>
24 changes: 24 additions & 0 deletions src/main/java/org/apache/ibatis/mapping/BoundSql.java
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -10,15 +12,21 @@ public class BoundSql {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;

public BoundSql(String sql) {
this(sql, null, null);
}

public BoundSql(String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
this(sql, parameterMappings, parameterObject, new HashMap<>());
}

public BoundSql(String sql, List<ParameterMapping> parameterMappings, Object parameterObject, Map<String, Object> additionalParameters) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = additionalParameters;
}

public List<ParameterMapping> getParameterMappings() {
Expand All @@ -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<String, Object> getAdditionalParameters() {
return additionalParameters;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.apache.ibatis.scripting.xmltags;

import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;

/**
Expand All @@ -9,6 +11,7 @@ public class DynamicContext {

private final StringJoiner sqlBuilder = new StringJoiner(" ");
private Object parameterObject;
private final Map<String, Object> bindings = new HashMap<>();

public DynamicContext() {
}
Expand All @@ -32,4 +35,12 @@ public Object getParameterObject() {
public void setParameterObject(Object parameterObject) {
this.parameterObject = parameterObject;
}

public Map<String, Object> getBindings() {
return bindings;
}

public void bind(String key, Object value) {
bindings.put(key, value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
157 changes: 157 additions & 0 deletions src/main/java/org/apache/ibatis/scripting/xmltags/ForeachSqlNode.java
Original file line number Diff line number Diff line change
@@ -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<String, Object> 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<String, Object> 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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -96,4 +97,20 @@ public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
targetContents.add(trim);
}
}

private class ForeachHandler implements NodeHandler {

@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> 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);
}
}

}
4 changes: 4 additions & 0 deletions src/test/java/org/apache/ibatis/dao/UserMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ public interface UserMapper {
List<UserDO> selectResultMap();

List<UserDTO> selectNestedResultMap();

List<UserDO> listDynamic(@Param("name") String name, @Param("age") Integer age);

List<UserDO> listForeach(@Param("ids") List<Long> ids);
}
39 changes: 39 additions & 0 deletions src/test/java/org/apache/ibatis/session/SqlSessionTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.apache.ibatis.session;

import java.util.ArrayList;
import java.util.List;

import org.apache.ibatis.DataSourceBuilderTest;
Expand Down Expand Up @@ -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<UserDO> list1 = userMapper.listDynamic(null, null);
Assert.assertNotNull(list1);
List<UserDO> list2 = userMapper.listDynamic("Sam", null);
Assert.assertNotNull(list2);
List<UserDO> list3 = userMapper.listDynamic(null, 20);
Assert.assertNotNull(list3);
List<UserDO> list4 = userMapper.listDynamic("Sam", 20);
Assert.assertNotNull(list4);
}

/**
* foreach标签测试
*/
@Test
public void queryForeachTag() {
UserMapper userMapper = getUserMapper();
List<Long> idList = new ArrayList<>();
idList.add(1L);
idList.add(2L);
List<UserDO> 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);
}

}
24 changes: 24 additions & 0 deletions src/test/resources/mapper/UserMapper.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,30 @@
LEFT JOIN car ON relation.car_id = car.id
</select>

<select id="listDynamic" resultType="org.apache.ibatis.entity.UserDO">
SELECT
name,
age
FROM user
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null">
and name = #{name}
</if>
<if test="age != null">
and age = #{age}
</if>
</trim>
</select>

<select id="listForeach" resultType="org.apache.ibatis.entity.UserDO">
SELECT
name,
age
FROM user
WHERE id IN
<foreach collection="ids" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>

</mapper>

0 comments on commit 083c167

Please sign in to comment.