Skip to content

Commit 060a397

Browse files
authored
Merge pull request #1856 from kazuki43zoo/gh-1237
Allow using actual argument name as bind parameter on a single collection
2 parents dc02ced + ce14cec commit 060a397

File tree

6 files changed

+371
-16
lines changed

6 files changed

+371
-16
lines changed

src/main/java/org/apache/ibatis/reflection/ParamNameResolver.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717

1818
import java.lang.annotation.Annotation;
1919
import java.lang.reflect.Method;
20+
import java.util.Collection;
2021
import java.util.Collections;
22+
import java.util.List;
2123
import java.util.Map;
24+
import java.util.Optional;
2225
import java.util.SortedMap;
2326
import java.util.TreeMap;
2427

@@ -32,6 +35,8 @@ public class ParamNameResolver {
3235

3336
public static final String GENERIC_NAME_PREFIX = "param";
3437

38+
private final boolean useActualParamName;
39+
3540
/**
3641
* <p>
3742
* The key is the index and the value is the name of the parameter.<br />
@@ -50,6 +55,7 @@ public class ParamNameResolver {
5055
private boolean hasParamAnnotation;
5156

5257
public ParamNameResolver(Configuration config, Method method) {
58+
this.useActualParamName = config.isUseActualParamName();
5359
final Class<?>[] paramTypes = method.getParameterTypes();
5460
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
5561
final SortedMap<Integer, String> map = new TreeMap<>();
@@ -70,7 +76,7 @@ public ParamNameResolver(Configuration config, Method method) {
7076
}
7177
if (name == null) {
7278
// @Param was not specified.
73-
if (config.isUseActualParamName()) {
79+
if (useActualParamName) {
7480
name = getActualParamName(method, paramIndex);
7581
}
7682
if (name == null) {
@@ -118,7 +124,8 @@ public Object getNamedParams(Object[] args) {
118124
if (args == null || paramCount == 0) {
119125
return null;
120126
} else if (!hasParamAnnotation && paramCount == 1) {
121-
return args[names.firstKey()];
127+
Object value = args[names.firstKey()];
128+
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
122129
} else {
123130
final Map<String, Object> param = new ParamMap<>();
124131
int i = 0;
@@ -135,4 +142,32 @@ public Object getNamedParams(Object[] args) {
135142
return param;
136143
}
137144
}
145+
146+
/**
147+
* Wrap to a {@link ParamMap} if object is {@link Collection} or array.
148+
*
149+
* @param object a parameter object
150+
* @param actualParamName an actual parameter name
151+
* (If specify a name, set an object to {@link ParamMap} with specified name)
152+
* @return a {@link ParamMap}
153+
* @since 3.5.5
154+
*/
155+
public static Object wrapToMapIfCollection(Object object, String actualParamName) {
156+
if (object instanceof Collection) {
157+
ParamMap<Object> map = new ParamMap<>();
158+
map.put("collection", object);
159+
if (object instanceof List) {
160+
map.put("list", object);
161+
}
162+
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
163+
return map;
164+
} else if (object != null && object.getClass().isArray()) {
165+
ParamMap<Object> map = new ParamMap<>();
166+
map.put("array", object);
167+
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
168+
return map;
169+
}
170+
return object;
171+
}
172+
138173
}

src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2009-2019 the original author or authors.
2+
* Copyright 2009-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@
3434
import org.apache.ibatis.executor.result.DefaultMapResultHandler;
3535
import org.apache.ibatis.executor.result.DefaultResultContext;
3636
import org.apache.ibatis.mapping.MappedStatement;
37+
import org.apache.ibatis.reflection.ParamNameResolver;
3738
import org.apache.ibatis.session.Configuration;
3839
import org.apache.ibatis.session.ResultHandler;
3940
import org.apache.ibatis.session.RowBounds;
@@ -317,21 +318,13 @@ private boolean isCommitOrRollbackRequired(boolean force) {
317318
}
318319

319320
private Object wrapCollection(final Object object) {
320-
if (object instanceof Collection) {
321-
StrictMap<Object> map = new StrictMap<>();
322-
map.put("collection", object);
323-
if (object instanceof List) {
324-
map.put("list", object);
325-
}
326-
return map;
327-
} else if (object != null && object.getClass().isArray()) {
328-
StrictMap<Object> map = new StrictMap<>();
329-
map.put("array", object);
330-
return map;
331-
}
332-
return object;
321+
return ParamNameResolver.wrapToMapIfCollection(object, null);
333322
}
334323

324+
/**
325+
* @deprecated Since 3.5.5
326+
*/
327+
@Deprecated
335328
public static class StrictMap<V> extends HashMap<String, V> {
336329

337330
private static final long serialVersionUID = -5741767162221585340L;
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/**
2+
* Copyright 2009-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.apache.ibatis.submitted.param_name_resolve;
17+
18+
import org.apache.ibatis.annotations.Select;
19+
import org.apache.ibatis.io.Resources;
20+
import org.apache.ibatis.jdbc.ScriptRunner;
21+
import org.apache.ibatis.session.SqlSession;
22+
import org.apache.ibatis.session.SqlSessionFactory;
23+
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
24+
import org.junit.jupiter.api.BeforeAll;
25+
import org.junit.jupiter.api.Test;
26+
27+
import java.io.Reader;
28+
import java.sql.Connection;
29+
import java.util.Arrays;
30+
import java.util.List;
31+
32+
import static org.junit.Assert.assertEquals;
33+
34+
class ActualParamNameTest {
35+
36+
private static SqlSessionFactory sqlSessionFactory;
37+
38+
@BeforeAll
39+
static void setUp() throws Exception {
40+
// create an SqlSessionFactory
41+
try (Reader reader = Resources
42+
.getResourceAsReader("org/apache/ibatis/submitted/param_name_resolve/mybatis-config.xml")) {
43+
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
44+
sqlSessionFactory.getConfiguration().addMapper(Mapper.class);
45+
}
46+
47+
// populate in-memory database
48+
try (Connection conn = sqlSessionFactory.getConfiguration().getEnvironment().getDataSource().getConnection();
49+
Reader reader = Resources
50+
.getResourceAsReader("org/apache/ibatis/submitted/param_name_resolve/CreateDB.sql")) {
51+
ScriptRunner runner = new ScriptRunner(conn);
52+
runner.setLogWriter(null);
53+
runner.runScript(reader);
54+
}
55+
}
56+
57+
@Test
58+
void testSingleListParameterWhenUseActualParamNameIsTrue() {
59+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
60+
Mapper mapper = sqlSession.getMapper(Mapper.class);
61+
// use actual name
62+
{
63+
long count = mapper.getUserCountUsingList(Arrays.asList(1, 2));
64+
assertEquals(2, count);
65+
}
66+
// use 'collection' as alias
67+
{
68+
long count = mapper.getUserCountUsingListWithAliasIsCollection(Arrays.asList(1, 2));
69+
assertEquals(2, count);
70+
}
71+
// use 'list' as alias
72+
{
73+
long count = mapper.getUserCountUsingListWithAliasIsList(Arrays.asList(1, 2));
74+
assertEquals(2, count);
75+
}
76+
}
77+
}
78+
79+
@Test
80+
void testSingleArrayParameterWhenUseActualParamNameIsTrue() {
81+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
82+
Mapper mapper = sqlSession.getMapper(Mapper.class);
83+
// use actual name
84+
{
85+
long count = mapper.getUserCountUsingArray(1, 2);
86+
assertEquals(2, count);
87+
}
88+
// use 'array' as alias
89+
{
90+
long count = mapper.getUserCountUsingArrayWithAliasArray(1, 2);
91+
assertEquals(2, count);
92+
}
93+
}
94+
}
95+
96+
interface Mapper {
97+
@Select({
98+
"<script>",
99+
" select count(*) from users u where u.id in",
100+
" <foreach item='item' index='index' collection='ids' open='(' separator=',' close=')'>",
101+
" #{item}",
102+
" </foreach>",
103+
"</script>"
104+
})
105+
Long getUserCountUsingList(List<Integer> ids);
106+
107+
@Select({
108+
"<script>",
109+
" select count(*) from users u where u.id in",
110+
" <foreach item='item' index='index' collection='collection' open='(' separator=',' close=')'>",
111+
" #{item}",
112+
" </foreach>",
113+
"</script>"
114+
})
115+
Long getUserCountUsingListWithAliasIsCollection(List<Integer> ids);
116+
117+
@Select({
118+
"<script>",
119+
" select count(*) from users u where u.id in",
120+
" <foreach item='item' index='index' collection='list' open='(' separator=',' close=')'>",
121+
" #{item}",
122+
" </foreach>",
123+
"</script>"
124+
})
125+
Long getUserCountUsingListWithAliasIsList(List<Integer> ids);
126+
127+
@Select({
128+
"<script>",
129+
" select count(*) from users u where u.id in",
130+
" <foreach item='item' index='index' collection='ids' open='(' separator=',' close=')'>",
131+
" #{item}",
132+
" </foreach>",
133+
"</script>"
134+
})
135+
Long getUserCountUsingArray(Integer... ids);
136+
137+
@Select({
138+
"<script>",
139+
" select count(*) from users u where u.id in",
140+
" <foreach item='item' index='index' collection='array' open='(' separator=',' close=')'>",
141+
" #{item}",
142+
" </foreach>",
143+
"</script>"
144+
})
145+
Long getUserCountUsingArrayWithAliasArray(Integer... ids);
146+
}
147+
148+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--
2+
-- Copyright 2009-2020 the original author or authors.
3+
--
4+
-- Licensed under the Apache License, Version 2.0 (the "License");
5+
-- you may not use this file except in compliance with the License.
6+
-- You may obtain a copy of the License at
7+
--
8+
-- http://www.apache.org/licenses/LICENSE-2.0
9+
--
10+
-- Unless required by applicable law or agreed to in writing, software
11+
-- distributed under the License is distributed on an "AS IS" BASIS,
12+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
-- See the License for the specific language governing permissions and
14+
-- limitations under the License.
15+
--
16+
17+
drop table users if exists;
18+
19+
create table users (
20+
id int,
21+
name varchar(20)
22+
);
23+
24+
insert into users (id, name) values (1, 'User1'), (2, 'User2'), (3, 'User3');

0 commit comments

Comments
 (0)