diff --git a/src/main/java/org/apache/ibatis/reflection/ParamNameResolver.java b/src/main/java/org/apache/ibatis/reflection/ParamNameResolver.java index 4d111f2d70e..ee3245382ac 100644 --- a/src/main/java/org/apache/ibatis/reflection/ParamNameResolver.java +++ b/src/main/java/org/apache/ibatis/reflection/ParamNameResolver.java @@ -17,8 +17,11 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.SortedMap; import java.util.TreeMap; @@ -32,6 +35,8 @@ public class ParamNameResolver { public static final String GENERIC_NAME_PREFIX = "param"; + private final boolean useActualParamName; + /** *

* The key is the index and the value is the name of the parameter.
@@ -50,6 +55,7 @@ public class ParamNameResolver { private boolean hasParamAnnotation; public ParamNameResolver(Configuration config, Method method) { + this.useActualParamName = config.isUseActualParamName(); final Class[] paramTypes = method.getParameterTypes(); final Annotation[][] paramAnnotations = method.getParameterAnnotations(); final SortedMap map = new TreeMap<>(); @@ -70,7 +76,7 @@ public ParamNameResolver(Configuration config, Method method) { } if (name == null) { // @Param was not specified. - if (config.isUseActualParamName()) { + if (useActualParamName) { name = getActualParamName(method, paramIndex); } if (name == null) { @@ -118,7 +124,8 @@ public Object getNamedParams(Object[] args) { if (args == null || paramCount == 0) { return null; } else if (!hasParamAnnotation && paramCount == 1) { - return args[names.firstKey()]; + Object value = args[names.firstKey()]; + return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null); } else { final Map param = new ParamMap<>(); int i = 0; @@ -135,4 +142,32 @@ public Object getNamedParams(Object[] args) { return param; } } + + /** + * Wrap to a {@link ParamMap} if object is {@link Collection} or array. + * + * @param object a parameter object + * @param actualParamName an actual parameter name + * (If specify a name, set an object to {@link ParamMap} with specified name) + * @return a {@link ParamMap} + * @since 3.5.5 + */ + public static Object wrapToMapIfCollection(Object object, String actualParamName) { + if (object instanceof Collection) { + ParamMap map = new ParamMap<>(); + map.put("collection", object); + if (object instanceof List) { + map.put("list", object); + } + Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object)); + return map; + } else if (object != null && object.getClass().isArray()) { + ParamMap map = new ParamMap<>(); + map.put("array", object); + Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object)); + return map; + } + return object; + } + } diff --git a/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java b/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java index 9cb9d4405fe..a1e30ac0043 100644 --- a/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java +++ b/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java @@ -1,5 +1,5 @@ /** - * Copyright 2009-2019 the original author or authors. + * Copyright 2009-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ import org.apache.ibatis.executor.result.DefaultMapResultHandler; import org.apache.ibatis.executor.result.DefaultResultContext; import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.reflection.ParamNameResolver; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; @@ -317,21 +318,13 @@ private boolean isCommitOrRollbackRequired(boolean force) { } private Object wrapCollection(final Object object) { - if (object instanceof Collection) { - StrictMap map = new StrictMap<>(); - map.put("collection", object); - if (object instanceof List) { - map.put("list", object); - } - return map; - } else if (object != null && object.getClass().isArray()) { - StrictMap map = new StrictMap<>(); - map.put("array", object); - return map; - } - return object; + return ParamNameResolver.wrapToMapIfCollection(object, null); } + /** + * @deprecated Since 3.5.5 + */ + @Deprecated public static class StrictMap extends HashMap { private static final long serialVersionUID = -5741767162221585340L; diff --git a/src/test/java/org/apache/ibatis/submitted/param_name_resolve/ActualParamNameTest.java b/src/test/java/org/apache/ibatis/submitted/param_name_resolve/ActualParamNameTest.java new file mode 100644 index 00000000000..2d0af704a4d --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/param_name_resolve/ActualParamNameTest.java @@ -0,0 +1,148 @@ +/** + * Copyright 2009-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.param_name_resolve; + +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.jdbc.ScriptRunner; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.Reader; +import java.sql.Connection; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +class ActualParamNameTest { + + private static SqlSessionFactory sqlSessionFactory; + + @BeforeAll + static void setUp() throws Exception { + // create an SqlSessionFactory + try (Reader reader = Resources + .getResourceAsReader("org/apache/ibatis/submitted/param_name_resolve/mybatis-config.xml")) { + sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); + sqlSessionFactory.getConfiguration().addMapper(Mapper.class); + } + + // populate in-memory database + try (Connection conn = sqlSessionFactory.getConfiguration().getEnvironment().getDataSource().getConnection(); + Reader reader = Resources + .getResourceAsReader("org/apache/ibatis/submitted/param_name_resolve/CreateDB.sql")) { + ScriptRunner runner = new ScriptRunner(conn); + runner.setLogWriter(null); + runner.runScript(reader); + } + } + + @Test + void testSingleListParameterWhenUseActualParamNameIsTrue() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); + // use actual name + { + long count = mapper.getUserCountUsingList(Arrays.asList(1, 2)); + assertEquals(2, count); + } + // use 'collection' as alias + { + long count = mapper.getUserCountUsingListWithAliasIsCollection(Arrays.asList(1, 2)); + assertEquals(2, count); + } + // use 'list' as alias + { + long count = mapper.getUserCountUsingListWithAliasIsList(Arrays.asList(1, 2)); + assertEquals(2, count); + } + } + } + + @Test + void testSingleArrayParameterWhenUseActualParamNameIsTrue() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); + // use actual name + { + long count = mapper.getUserCountUsingArray(1, 2); + assertEquals(2, count); + } + // use 'array' as alias + { + long count = mapper.getUserCountUsingArrayWithAliasArray(1, 2); + assertEquals(2, count); + } + } + } + + interface Mapper { + @Select({ + "" + }) + Long getUserCountUsingList(List ids); + + @Select({ + "" + }) + Long getUserCountUsingListWithAliasIsCollection(List ids); + + @Select({ + "" + }) + Long getUserCountUsingListWithAliasIsList(List ids); + + @Select({ + "" + }) + Long getUserCountUsingArray(Integer... ids); + + @Select({ + "" + }) + Long getUserCountUsingArrayWithAliasArray(Integer... ids); + } + +} diff --git a/src/test/java/org/apache/ibatis/submitted/param_name_resolve/CreateDB.sql b/src/test/java/org/apache/ibatis/submitted/param_name_resolve/CreateDB.sql new file mode 100644 index 00000000000..abff5cf3b5a --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/param_name_resolve/CreateDB.sql @@ -0,0 +1,24 @@ +-- +-- Copyright 2009-2020 the original author or authors. +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +drop table users if exists; + +create table users ( + id int, + name varchar(20) +); + +insert into users (id, name) values (1, 'User1'), (2, 'User2'), (3, 'User3'); diff --git a/src/test/java/org/apache/ibatis/submitted/param_name_resolve/NoActualParamNameTest.java b/src/test/java/org/apache/ibatis/submitted/param_name_resolve/NoActualParamNameTest.java new file mode 100644 index 00000000000..d9a3246ee32 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/param_name_resolve/NoActualParamNameTest.java @@ -0,0 +1,119 @@ +/** + * Copyright 2009-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.param_name_resolve; + +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.exceptions.PersistenceException; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.jdbc.ScriptRunner; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.Reader; +import java.sql.Connection; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +class NoActualParamNameTest { + + private static SqlSessionFactory sqlSessionFactory; + + @BeforeAll + static void setUp() throws Exception { + // create an SqlSessionFactory + try (Reader reader = Resources + .getResourceAsReader("org/apache/ibatis/submitted/param_name_resolve/mybatis-config.xml")) { + sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); + sqlSessionFactory.getConfiguration().addMapper(Mapper.class); + sqlSessionFactory.getConfiguration().setUseActualParamName(false); + } + + // populate in-memory database + try (Connection conn = sqlSessionFactory.getConfiguration().getEnvironment().getDataSource().getConnection(); + Reader reader = Resources + .getResourceAsReader("org/apache/ibatis/submitted/param_name_resolve/CreateDB.sql")) { + ScriptRunner runner = new ScriptRunner(conn); + runner.setLogWriter(null); + runner.runScript(reader); + } + } + + @Test + void testSingleListParameterWhenUseActualParamNameIsFalse() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); + // use actual name -> no available and index parameter("0") is not available too + { + try { + mapper.getUserCountUsingList(Arrays.asList(1, 2)); + fail(); + } catch (PersistenceException e) { + assertEquals("Parameter 'ids' not found. Available parameters are [collection, list]", e.getCause().getMessage()); + } + } + // use 'collection' as alias + { + long count = mapper.getUserCountUsingListWithAliasIsCollection(Arrays.asList(1, 2)); + assertEquals(2, count); + } + // use 'list' as alias + { + long count = mapper.getUserCountUsingListWithAliasIsList(Arrays.asList(1, 2)); + assertEquals(2, count); + } + } + } + + interface Mapper { + @Select({ + "" + }) + Long getUserCountUsingList(List ids); + + @Select({ + "" + }) + Long getUserCountUsingListWithAliasIsCollection(List ids); + + @Select({ + "" + }) + Long getUserCountUsingListWithAliasIsList(List ids); + + } + +} diff --git a/src/test/java/org/apache/ibatis/submitted/param_name_resolve/mybatis-config.xml b/src/test/java/org/apache/ibatis/submitted/param_name_resolve/mybatis-config.xml new file mode 100644 index 00000000000..107f2b9e370 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/param_name_resolve/mybatis-config.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + +