Skip to content

Commit 01ee321

Browse files
committed
Support some reserved argument types on sql provider method
* Type of Mapper interface * Method of mapper method * Configuration of MyBatis Fixes gh-1013
1 parent 6f9105e commit 01ee321

File tree

8 files changed

+253
-17
lines changed

8 files changed

+253
-17
lines changed

src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterT
465465
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
466466
} else if (sqlProviderAnnotationType != null) {
467467
Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
468-
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
468+
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
469469
}
470470
return null;
471471
} catch (Exception e) {

src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.lang.reflect.Method;
1919
import java.util.HashMap;
20+
import java.util.LinkedHashMap;
2021
import java.util.Map;
2122

2223
import org.apache.ibatis.builder.BuilderException;
@@ -34,16 +35,26 @@ public class ProviderSqlSource implements SqlSource {
3435

3536
private final SqlSourceBuilder sqlSourceParser;
3637
private final Class<?> providerType;
38+
private final Map<Integer, Object> reservedParameterPositionMapping = new LinkedHashMap<Integer, Object>();
39+
private final Object[] providerMethodArgumentsForReservedParameterOnly;
40+
private final int userParameterCount;
41+
private final Integer firstPositionOfUserParameter;
3742
private Method providerMethod;
3843
private String[] providerMethodArgumentNames;
3944

40-
public ProviderSqlSource(Configuration config, Object provider) {
45+
public ProviderSqlSource(Configuration configuration, Object provider) {
46+
this(configuration, provider, null, null);
47+
}
48+
49+
/**
50+
* @since 3.4.5
51+
*/
52+
public ProviderSqlSource(Configuration configuration, Object provider, Class<?> mapperInterface, Method mapperMethod) {
4153
String providerMethodName;
4254
try {
43-
this.sqlSourceParser = new SqlSourceBuilder(config);
55+
this.sqlSourceParser = new SqlSourceBuilder(configuration);
4456
this.providerType = (Class<?>) provider.getClass().getMethod("type").invoke(provider);
4557
providerMethodName = (String) provider.getClass().getMethod("method").invoke(provider);
46-
4758
for (Method m : this.providerType.getMethods()) {
4859
if (providerMethodName.equals(m.getName())) {
4960
if (m.getReturnType() == String.class) {
@@ -53,7 +64,7 @@ public ProviderSqlSource(Configuration config, Object provider) {
5364
+ "'. Sql provider method can not overload.");
5465
}
5566
this.providerMethod = m;
56-
this.providerMethodArgumentNames = new ParamNameResolver(config, m).getNames();
67+
this.providerMethodArgumentNames = new ParamNameResolver(configuration, m).getNames();
5768
}
5869
}
5970
}
@@ -66,6 +77,23 @@ public ProviderSqlSource(Configuration config, Object provider) {
6677
throw new BuilderException("Error creating SqlSource for SqlProvider. Method '"
6778
+ providerMethodName + "' not found in SqlProvider '" + this.providerType.getName() + "'.");
6879
}
80+
Class<?>[] parameterTypes = providerMethod.getParameterTypes();
81+
Integer firstPositionOfUserDefineType = null;
82+
for (int i = 0; i< parameterTypes.length; i++) {
83+
Class<?> parameterType = parameterTypes[i];
84+
if (parameterType == Class.class) {
85+
reservedParameterPositionMapping.put(i, mapperInterface);
86+
} else if (parameterType == Method.class) {
87+
reservedParameterPositionMapping.put(i, mapperMethod);
88+
} else if (Configuration.class.isAssignableFrom(parameterType)) {
89+
reservedParameterPositionMapping.put(i, configuration);
90+
} else if (firstPositionOfUserDefineType == null) {
91+
firstPositionOfUserDefineType = i;
92+
}
93+
}
94+
this.providerMethodArgumentsForReservedParameterOnly = reservedParameterPositionMapping.values().toArray();
95+
this.userParameterCount = parameterTypes.length - reservedParameterPositionMapping.size();
96+
this.firstPositionOfUserParameter = firstPositionOfUserDefineType;
6997
}
7098

7199
@Override
@@ -80,9 +108,15 @@ private SqlSource createSqlSource(Object parameterObject) {
80108
String sql;
81109
if (parameterTypes.length == 0) {
82110
sql = (String) providerMethod.invoke(providerType.newInstance());
83-
} else if (parameterTypes.length == 1 &&
84-
(parameterObject == null || parameterTypes[0].isAssignableFrom(parameterObject.getClass()))) {
85-
sql = (String) providerMethod.invoke(providerType.newInstance(), parameterObject);
111+
} else if (userParameterCount == 0) {
112+
sql = (String) providerMethod.invoke(providerType.newInstance(), providerMethodArgumentsForReservedParameterOnly);
113+
} else if (userParameterCount == 1 &&
114+
(parameterObject == null || parameterTypes[firstPositionOfUserParameter].isAssignableFrom(parameterObject.getClass()))) {
115+
if (reservedParameterPositionMapping.isEmpty()) {
116+
sql = (String) providerMethod.invoke(providerType.newInstance(), parameterObject);
117+
} else {
118+
sql = (String) providerMethod.invoke(providerType.newInstance(), extractProviderMethodArguments(parameterObject, parameterTypes.length));
119+
}
86120
} else if (parameterObject instanceof Map) {
87121
@SuppressWarnings("unchecked")
88122
Map<String, Object> params = (Map<String, Object>) parameterObject;
@@ -91,7 +125,7 @@ private SqlSource createSqlSource(Object parameterObject) {
91125
throw new BuilderException("Error invoking SqlProvider method ("
92126
+ providerType.getName() + "." + providerMethod.getName()
93127
+ "). Cannot invoke a method that holds "
94-
+ (parameterTypes.length == 1 ? "named argument(@Param)": "multiple arguments")
128+
+ (userParameterCount == 1 ? "named argument(@Param)": "multiple arguments")
95129
+ " using a specifying parameterObject. In this case, please specify a 'java.util.Map' object.");
96130
}
97131
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
@@ -105,10 +139,23 @@ private SqlSource createSqlSource(Object parameterObject) {
105139
}
106140
}
107141

142+
private Object[] extractProviderMethodArguments(Object parameterObject, int length) {
143+
Object[] args = new Object[length];
144+
args[firstPositionOfUserParameter] = parameterObject;
145+
for(Map.Entry<Integer, Object> entry : reservedParameterPositionMapping.entrySet()) {
146+
args[entry.getKey()] = entry.getValue();
147+
}
148+
return args;
149+
}
150+
108151
private Object[] extractProviderMethodArguments(Map<String, Object> params, String[] argumentNames) {
109152
Object[] args = new Object[argumentNames.length];
110153
for (int i = 0; i < args.length; i++) {
111-
args[i] = params.get(argumentNames[i]);
154+
if (reservedParameterPositionMapping.containsKey(i)) {
155+
args[i] = reservedParameterPositionMapping.get(i);
156+
} else {
157+
args[i] = params.get(argumentNames[i]);
158+
}
112159
}
113160
return args;
114161
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* Copyright 2009-2017 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.sqlprovider;
17+
18+
import org.apache.ibatis.annotations.Param;
19+
import org.apache.ibatis.annotations.SelectProvider;
20+
21+
import java.lang.annotation.ElementType;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
import java.util.List;
26+
27+
public interface BaseMapper<T> {
28+
29+
@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdReservedParamOnly")
30+
@ContainsLogicalDelete
31+
T selectById(Integer id);
32+
33+
@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdReservedParamOnly")
34+
T selectActiveById(Integer id);
35+
36+
@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByNameOneParamAndReservedParam")
37+
@ContainsLogicalDelete
38+
List<T> selectByName(String name);
39+
40+
@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByNameOneParamAndReservedParam")
41+
List<T> selectActiveByName(String name);
42+
43+
@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdAndNameMultipleParamAndReservedParamWithAtParam")
44+
@ContainsLogicalDelete
45+
List<T> selectByIdAndNameWithAtParam(@Param("id") Integer id, @Param("name") String name);
46+
47+
@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdAndNameMultipleParamAndReservedParamWithAtParam")
48+
List<T> selectActiveByIdAndNameWithAtParam(@Param("id") Integer id, @Param("name") String name);
49+
50+
@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdAndNameMultipleParamAndReservedParam")
51+
@ContainsLogicalDelete
52+
List<T> selectByIdAndName(Integer id, String name);
53+
54+
@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdAndNameMultipleParamAndReservedParam")
55+
List<T> selectActiveByIdAndName(Integer id, String name);
56+
57+
@Retention(RetentionPolicy.RUNTIME)
58+
@Target(ElementType.METHOD)
59+
@interface ContainsLogicalDelete {
60+
boolean value() default false;
61+
}
62+
63+
@Retention(RetentionPolicy.RUNTIME)
64+
@Target(ElementType.TYPE)
65+
@interface Meta {
66+
String tableName();
67+
}
68+
69+
}

src/test/java/org/apache/ibatis/submitted/sqlprovider/CreateDB.sql

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
--
2-
-- Copyright 2009-2016 the original author or authors.
2+
-- Copyright 2009-2017 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.
@@ -18,11 +18,12 @@ drop table users if exists;
1818

1919
create table users (
2020
id int,
21-
name varchar(20)
21+
name varchar(20),
22+
logical_delete boolean default false
2223
);
2324

2425
insert into users (id, name) values(1, 'User1');
2526
insert into users (id, name) values(2, 'User2');
2627
insert into users (id, name) values(3, 'User3');
27-
insert into users (id, name) values(4, 'User4');
28+
insert into users (id, name, logical_delete) values(4, 'User4', true);
2829

src/test/java/org/apache/ibatis/submitted/sqlprovider/Mapper.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2009-2016 the original author or authors.
2+
* Copyright 2009-2017 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.
@@ -24,7 +24,8 @@
2424
import org.apache.ibatis.annotations.SelectProvider;
2525
import org.apache.ibatis.annotations.UpdateProvider;
2626

27-
public interface Mapper {
27+
@BaseMapper.Meta(tableName = "users")
28+
public interface Mapper extends BaseMapper<User> {
2829
@SelectProvider(type = OurSqlBuilder.class, method = "buildGetUsersQuery")
2930
List<User> getUsers(List<Integer> allFilterIds);
3031

src/test/java/org/apache/ibatis/submitted/sqlprovider/OurSqlBuilder.java

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2009-2016 the original author or authors.
2+
* Copyright 2009-2017 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.
@@ -17,7 +17,9 @@
1717

1818
import org.apache.ibatis.annotations.Param;
1919
import org.apache.ibatis.jdbc.SQL;
20+
import org.apache.ibatis.session.Configuration;
2021

22+
import java.lang.reflect.Method;
2123
import java.util.List;
2224
import java.util.Map;
2325

@@ -148,4 +150,68 @@ public String buildDelete() {
148150
return "delete from users where id = #{id}";
149151
}
150152

153+
public String buildSelectByIdReservedParamOnly(Method mapperMethod, Class<?> mapperInterface, final Configuration configuration) {
154+
final boolean containsLogicalDelete = mapperMethod.getAnnotation(BaseMapper.ContainsLogicalDelete.class) != null;
155+
final String tableName = mapperInterface.getAnnotation(BaseMapper.Meta.class).tableName();
156+
return new SQL(){{
157+
SELECT("*");
158+
FROM(tableName);
159+
WHERE("id = #{id}");
160+
if (!containsLogicalDelete){
161+
WHERE("logical_delete = " + configuration.getVariables().getProperty("boolean_value_false"));
162+
}
163+
}}.toString();
164+
}
165+
166+
public String buildSelectByNameOneParamAndReservedParam(final Configuration configuration, Class<?> mapperInterface, Method mapperMethod, final String name) {
167+
final boolean containsLogicalDelete = mapperMethod.getAnnotation(BaseMapper.ContainsLogicalDelete.class) != null;
168+
final String tableName = mapperInterface.getAnnotation(BaseMapper.Meta.class).tableName();
169+
return new SQL(){{
170+
SELECT("*");
171+
FROM(tableName);
172+
if (name != null) {
173+
WHERE("name like #{name} || '%'");
174+
}
175+
if (!containsLogicalDelete){
176+
WHERE("logical_delete = " + configuration.getVariables().getProperty("boolean_value_false"));
177+
}
178+
}}.toString();
179+
}
180+
181+
public String buildSelectByIdAndNameMultipleParamAndReservedParamWithAtParam(@Param("id") final Integer id, final Configuration configuration, Class<?> mapperInterface, Method mapperMethod, @Param("name") final String name) {
182+
final boolean containsLogicalDelete = mapperMethod.getAnnotation(BaseMapper.ContainsLogicalDelete.class) != null;
183+
final String tableName = mapperInterface.getAnnotation(BaseMapper.Meta.class).tableName();
184+
return new SQL(){{
185+
SELECT("*");
186+
FROM(tableName);
187+
if (id != null) {
188+
WHERE("id = #{id}");
189+
}
190+
if (name != null) {
191+
WHERE("name like #{name} || '%'");
192+
}
193+
if (!containsLogicalDelete){
194+
WHERE("logical_delete = " + configuration.getVariables().getProperty("boolean_value_false"));
195+
}
196+
}}.toString();
197+
}
198+
199+
public String buildSelectByIdAndNameMultipleParamAndReservedParam(final Integer id, final String name, final Configuration configuration, Class<?> mapperInterface, Method mapperMethod) {
200+
final boolean containsLogicalDelete = mapperMethod.getAnnotation(BaseMapper.ContainsLogicalDelete.class) != null;
201+
final String tableName = mapperInterface.getAnnotation(BaseMapper.Meta.class).tableName();
202+
return new SQL(){{
203+
SELECT("*");
204+
FROM(tableName);
205+
if (id != null) {
206+
WHERE("id = #{param1}");
207+
}
208+
if (name != null) {
209+
WHERE("name like #{param2} || '%'");
210+
}
211+
if (!containsLogicalDelete){
212+
WHERE("logical_delete = " + configuration.getVariables().getProperty("boolean_value_false"));
213+
}
214+
}}.toString();
215+
}
216+
151217
}

src/test/java/org/apache/ibatis/submitted/sqlprovider/SqlProviderTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,54 @@ public void shouldDeleteUser() {
399399
}
400400
}
401401

402+
@Test
403+
public void mapperReservedParamOnly() {
404+
SqlSession sqlSession = sqlSessionFactory.openSession();
405+
try {
406+
Mapper mapper = sqlSession.getMapper(Mapper.class);
407+
assertEquals("User4", mapper.selectById(4).getName());
408+
assertNull(mapper.selectActiveById(4));
409+
} finally {
410+
sqlSession.close();
411+
}
412+
}
413+
414+
@Test
415+
public void mapperOneParamAndReservedParam() {
416+
SqlSession sqlSession = sqlSessionFactory.openSession();
417+
try {
418+
Mapper mapper = sqlSession.getMapper(Mapper.class);
419+
assertEquals(1, mapper.selectByName("User4").size());
420+
assertEquals(0, mapper.selectActiveByName("User4").size());
421+
} finally {
422+
sqlSession.close();
423+
}
424+
}
425+
426+
@Test
427+
public void mapperMultipleParamAndReservedParamWithAtParam() {
428+
SqlSession sqlSession = sqlSessionFactory.openSession();
429+
try {
430+
Mapper mapper = sqlSession.getMapper(Mapper.class);
431+
assertEquals(1, mapper.selectByIdAndNameWithAtParam(4,"User4").size());
432+
assertEquals(0, mapper.selectActiveByIdAndNameWithAtParam(4,"User4").size());
433+
} finally {
434+
sqlSession.close();
435+
}
436+
}
437+
438+
@Test
439+
public void mapperMultipleParamAndReservedParam() {
440+
SqlSession sqlSession = sqlSessionFactory.openSession();
441+
try {
442+
Mapper mapper = sqlSession.getMapper(Mapper.class);
443+
assertEquals(1, mapper.selectByIdAndName(4,"User4").size());
444+
assertEquals(0, mapper.selectActiveByIdAndName(4,"User4").size());
445+
} finally {
446+
sqlSession.close();
447+
}
448+
}
449+
402450
public interface ErrorMapper {
403451
@SelectProvider(type = ErrorSqlBuilder.class, method = "methodNotFound")
404452
void methodNotFound();

0 commit comments

Comments
 (0)