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

+1
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

+2-1
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

+2-1
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

+16-1
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

+17-2
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

+2-1
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

+23
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

+14
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

+7-6
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

+14
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>

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

+7-6
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.
@@ -153,11 +153,12 @@ AND title like ‘someTitle’]]></source>
153153
<source><![CDATA[<select id="selectPostIn" resultType="domain.blog.Post">
154154
SELECT *
155155
FROM POST P
156-
WHERE ID in
157-
<foreach item="item" index="index" collection="list"
158-
open="(" separator="," close=")">
159-
#{item}
160-
</foreach>
156+
<where>
157+
<foreach item="item" index="index" collection="list"
158+
open="ID in (" separator="," close=")" nullable="true">
159+
#{item}
160+
</foreach>
161+
</where>
161162
</select>]]></source>
162163
<p><em>foreach</em> 要素は非常に強力で、イテレーション処理の対象となるコレクションを指定する collection と、ループ内で要素を格納する変数 item、ループ回数を格納する index 変数を宣言することができます。また、開始・終了の文字列とイテレーションの合間に出力する区切り文字を指定することもできます。foreach タグは賢いので、余分な区切り文字を出力することはありません。</p>
163164
<p><span class="label important">NOTE</span> collection には Iterable を実装したオブジェクト(List や Set など)の他に Map や Array を指定することもできます。collection に Iterable または Array を指定した場合、 index で指定した変数にはインデックスの数値、 item で指定した変数にはコレクション、配列の要素が格納されます。Map あるいは Map.Entry のコレクションを指定した場合は index にマップのキー、item にマップの値が格納されます。</p>

src/site/ko/xdoc/configuration.xml

+14
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,20 @@ SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environ
584584
설정하지 않음
585585
</td>
586586
</tr>
587+
<tr>
588+
<td>
589+
nullableOnForEach
590+
</td>
591+
<td>
592+
Specifies the default value of 'nullable' attribute on 'foreach' tag. (Since 3.5.9)
593+
</td>
594+
<td>
595+
true | false
596+
</td>
597+
<td>
598+
false
599+
</td>
600+
</tr>
587601
</tbody>
588602
</table>
589603
<p>위 설정을 모두 사용한 setting 엘리먼트의 예제이다:</p>

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

+7-6
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-2020 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.
@@ -176,11 +176,12 @@ AND title like ‘someTitle’]]></source>
176176
<source><![CDATA[<select id="selectPostIn" resultType="domain.blog.Post">
177177
SELECT *
178178
FROM POST P
179-
WHERE ID in
180-
<foreach item="item" index="index" collection="list"
181-
open="(" separator="," close=")">
182-
#{item}
183-
</foreach>
179+
<where>
180+
<foreach item="item" index="index" collection="list"
181+
open="ID in (" separator="," close=")" nullable="true">
182+
#{item}
183+
</foreach>
184+
</where>
184185
</select>]]></source>
185186
<p>foreach엘리먼트는 매우 강력하고 collection 을 명시하는 것을 허용한다.
186187
엘리먼트 내부에서 사용할 수 있는 item, index두가지 변수를 선언한다.

src/site/xdoc/configuration.xml

+14
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,20 @@ SqlSessionFactory factory =
664664
Not set
665665
</td>
666666
</tr>
667+
<tr>
668+
<td>
669+
nullableOnForEach
670+
</td>
671+
<td>
672+
Specifies the default value of 'nullable' attribute on 'foreach' tag. (Since 3.5.9)
673+
</td>
674+
<td>
675+
true | false
676+
</td>
677+
<td>
678+
false
679+
</td>
680+
</tr>
667681
</tbody>
668682
</table>
669683
<p>

src/site/xdoc/dynamic-sql.xml

+7-6
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.
@@ -146,11 +146,12 @@ AND title like ‘someTitle’]]></source>
146146
<source><![CDATA[<select id="selectPostIn" resultType="domain.blog.Post">
147147
SELECT *
148148
FROM POST P
149-
WHERE ID in
150-
<foreach item="item" index="index" collection="list"
151-
open="(" separator="," close=")">
152-
#{item}
153-
</foreach>
149+
<where>
150+
<foreach item="item" index="index" collection="list"
151+
open="ID in (" separator="," close=")" nullable="true">
152+
#{item}
153+
</foreach>
154+
</where>
154155
</select>]]></source>
155156
<p>The <em>foreach</em> element is very powerful, and allows you to specify a collection, declare item and index variables that can be used inside the body of the element. It also allows you to specify opening and closing strings, and add a separator to place in between iterations. The element is smart in that it won’t accidentally append extra separators. </p>
156157
<p><span class="label important">NOTE</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/zh/xdoc/configuration.xml

+14
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,20 @@ SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environ
595595
Not set
596596
</td>
597597
</tr>
598+
<tr>
599+
<td>
600+
nullableOnForEach
601+
</td>
602+
<td>
603+
Specifies the default value of 'nullable' attribute on 'foreach' tag. (Since 3.5.9)
604+
</td>
605+
<td>
606+
true | false
607+
</td>
608+
<td>
609+
false
610+
</td>
611+
</tr>
598612
</tbody>
599613
</table>
600614
<p>

0 commit comments

Comments
 (0)