Skip to content

Commit 253982f

Browse files
authored
Merge pull request #1883 from kazuki43zoo/allow-null-on-forEach
Add option for allowing null value on foreach tag
2 parents 7b5e450 + 7738026 commit 253982f

22 files changed

+283
-38
lines changed

src/main/java/org/apache/ibatis/builder/xml/XMLConfigBuilder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ private void settingsElement(Properties props) {
270270
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
271271
configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
272272
configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
273+
configuration.setNullableOnForEach(booleanValueOf(props.getProperty("nullableOnForEach"), false));
273274
}
274275

275276
private void environmentsElement(XNode context) throws Exception {

src/main/java/org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8" ?>
22
<!--
33
4-
Copyright 2009-2018 the original author or authors.
4+
Copyright 2009-2021 the original author or authors.
55
66
Licensed under the Apache License, Version 2.0 (the "License");
77
you may not use this file except in compliance with the License.
@@ -271,6 +271,7 @@ suffixOverrides CDATA #IMPLIED
271271
<!ELEMENT foreach (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
272272
<!ATTLIST foreach
273273
collection CDATA #REQUIRED
274+
nullable (true|false) #IMPLIED
274275
item CDATA #IMPLIED
275276
index CDATA #IMPLIED
276277
open CDATA #IMPLIED

src/main/java/org/apache/ibatis/builder/xml/mybatis-mapper.xsd

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!--
33
4-
Copyright 2009-2018 the original author or authors.
4+
Copyright 2009-2021 the original author or authors.
55
66
Licensed under the Apache License, Version 2.0 (the "License");
77
you may not use this file except in compliance with the License.
@@ -599,6 +599,7 @@
599599
<xs:element ref="bind"/>
600600
</xs:choice>
601601
<xs:attribute name="collection" use="required"/>
602+
<xs:attribute name="nullable" type="xs:boolean"/>
602603
<xs:attribute name="item"/>
603604
<xs:attribute name="index"/>
604605
<xs:attribute name="open"/>

src/main/java/org/apache/ibatis/scripting/xmltags/ExpressionEvaluator.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,25 @@ public boolean evaluateBoolean(String expression, Object parameterObject) {
3939
return value != null;
4040
}
4141

42+
/**
43+
* @deprecated Since 3.5.9, use the {@link #evaluateIterable(String, Object, boolean)}.
44+
*/
45+
@Deprecated
4246
public Iterable<?> evaluateIterable(String expression, Object parameterObject) {
47+
return evaluateIterable(expression, parameterObject, false);
48+
}
49+
50+
/**
51+
* @since 3.5.9
52+
*/
53+
public Iterable<?> evaluateIterable(String expression, Object parameterObject, boolean nullable) {
4354
Object value = OgnlCache.getValue(expression, parameterObject);
4455
if (value == null) {
45-
throw new BuilderException("The expression '" + expression + "' evaluated to a null value.");
56+
if (nullable) {
57+
return null;
58+
} else {
59+
throw new BuilderException("The expression '" + expression + "' evaluated to a null value.");
60+
}
4661
}
4762
if (value instanceof Iterable) {
4863
return (Iterable<?>) value;

src/main/java/org/apache/ibatis/scripting/xmltags/ForEachSqlNode.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.apache.ibatis.scripting.xmltags;
1717

1818
import java.util.Map;
19+
import java.util.Optional;
1920

2021
import org.apache.ibatis.parsing.GenericTokenParser;
2122
import org.apache.ibatis.session.Configuration;
@@ -28,6 +29,7 @@ public class ForEachSqlNode implements SqlNode {
2829

2930
private final ExpressionEvaluator evaluator;
3031
private final String collectionExpression;
32+
private final Boolean nullable;
3133
private final SqlNode contents;
3234
private final String open;
3335
private final String close;
@@ -36,9 +38,21 @@ public class ForEachSqlNode implements SqlNode {
3638
private final String index;
3739
private final Configuration configuration;
3840

41+
/**
42+
* @deprecated Since 3.5.9, use the {@link #ForEachSqlNode(Configuration, SqlNode, String, Boolean, String, String, String, String, String)}.
43+
*/
44+
@Deprecated
3945
public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, String index, String item, String open, String close, String separator) {
46+
this(configuration, contents, collectionExpression, null, index, item, open, close, separator);
47+
}
48+
49+
/**
50+
* @since 3.5.9
51+
*/
52+
public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, Boolean nullable, String index, String item, String open, String close, String separator) {
4053
this.evaluator = new ExpressionEvaluator();
4154
this.collectionExpression = collectionExpression;
55+
this.nullable = nullable;
4256
this.contents = contents;
4357
this.open = open;
4458
this.close = close;
@@ -51,8 +65,9 @@ public ForEachSqlNode(Configuration configuration, SqlNode contents, String coll
5165
@Override
5266
public boolean apply(DynamicContext context) {
5367
Map<String, Object> bindings = context.getBindings();
54-
final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
55-
if (!iterable.iterator().hasNext()) {
68+
final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings,
69+
Optional.ofNullable(nullable).orElseGet(configuration::isNullableOnForEach));
70+
if (iterable == null || !iterable.iterator().hasNext()) {
5671
return true;
5772
}
5873
boolean first = true;

src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,13 @@ public ForEachHandler() {
171171
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
172172
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
173173
String collection = nodeToHandle.getStringAttribute("collection");
174+
Boolean nullable = nodeToHandle.getBooleanAttribute("nullable");
174175
String item = nodeToHandle.getStringAttribute("item");
175176
String index = nodeToHandle.getStringAttribute("index");
176177
String open = nodeToHandle.getStringAttribute("open");
177178
String close = nodeToHandle.getStringAttribute("close");
178179
String separator = nodeToHandle.getStringAttribute("separator");
179-
ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);
180+
ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, nullable, index, item, open, close, separator);
180181
targetContents.add(forEachSqlNode);
181182
}
182183
}

src/main/java/org/apache/ibatis/session/Configuration.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ public class Configuration {
114114
protected boolean useActualParamName = true;
115115
protected boolean returnInstanceForEmptyRow;
116116
protected boolean shrinkWhitespacesInSql;
117+
protected boolean nullableOnForEach;
117118

118119
protected String logPrefix;
119120
protected Class<? extends Log> logImpl;
@@ -297,6 +298,28 @@ public void setShrinkWhitespacesInSql(boolean shrinkWhitespacesInSql) {
297298
this.shrinkWhitespacesInSql = shrinkWhitespacesInSql;
298299
}
299300

301+
/**
302+
* Sets the default value of 'nullable' attribute on 'foreach' tag.
303+
*
304+
* @param nullableOnForEach If nullable, set to {@code true}
305+
* @since 3.5.9
306+
*/
307+
public void setNullableOnForEach(boolean nullableOnForEach) {
308+
this.nullableOnForEach = nullableOnForEach;
309+
}
310+
311+
/**
312+
* Returns the default value of 'nullable' attribute on 'foreach' tag.
313+
*
314+
* <p>Default is {@code false}.
315+
*
316+
* @return If nullable, set to {@code true}
317+
* @since 3.5.9
318+
*/
319+
public boolean isNullableOnForEach() {
320+
return nullableOnForEach;
321+
}
322+
300323
public String getDatabaseId() {
301324
return databaseId;
302325
}

src/site/es/xdoc/configuration.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,20 @@ SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environ
577577
Not set
578578
</td>
579579
</tr>
580+
<tr>
581+
<td>
582+
nullableOnForEach
583+
</td>
584+
<td>
585+
Specifies the default value of 'nullable' attribute on 'foreach' tag. (Since 3.5.9)
586+
</td>
587+
<td>
588+
true | false
589+
</td>
590+
<td>
591+
false
592+
</td>
593+
</tr>
580594
</tbody>
581595
</table>
582596
<p>

src/site/es/xdoc/dynamic-sql.xml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!--
33
4-
Copyright 2009-2019 the original author or authors.
4+
Copyright 2009-2021 the original author or authors.
55
66
Licensed under the Apache License, Version 2.0 (the "License");
77
you may not use this file except in compliance with the License.
@@ -147,11 +147,12 @@ AND title like ‘someTitle’]]></source>
147147
<source><![CDATA[<select id="selectPostIn" resultType="domain.blog.Post">
148148
SELECT *
149149
FROM POST P
150-
WHERE ID in
151-
<foreach item="item" index="index" collection="list"
152-
open="(" separator="," close=")">
153-
#{item}
154-
</foreach>
150+
<where>
151+
<foreach item="item" index="index" collection="list"
152+
open="ID in (" separator="," close=")" nullable="true">
153+
#{item}
154+
</foreach>
155+
</where>
155156
</select>]]></source>
156157
<p>El elemento foreach es muy potente, permite especificar una colección y declarar variables elemento e índice que pueden usarse dentro del cuerpo del elemento. Permite también abrir y cerrar strings y añadir un separador entre las iteraciones. Este elemento es inteligente en tanto en cuanto no añade separadores extra accidentalmente.</p>
157158
<p><span class="label important">NOTA</span> You can pass any Iterable object (for example List, Set, etc.), as well as any Map or Array object to foreach as collection parameter. When using an Iterable or Array, index will be the number of current iteration and value item will be the element retrieved in this iteration. When using a Map (or Collection of Map.Entry objects), index will be the key object and item will be the value object.</p>

src/site/ja/xdoc/configuration.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,20 @@ SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environ
601601
未指定
602602
</td>
603603
</tr>
604+
<tr>
605+
<td>
606+
nullableOnForEach
607+
</td>
608+
<td>
609+
'foreach' タグの 'nullable' 属性のデフォルト値. (導入されたバージョン: 3.5.9)
610+
</td>
611+
<td>
612+
true | false
613+
</td>
614+
<td>
615+
false
616+
</td>
617+
</tr>
604618
</tbody>
605619
</table>
606620
<p>

0 commit comments

Comments
 (0)