1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.ibatis.binding;
17
18 import java.lang.reflect.Array;
19 import java.lang.reflect.Method;
20 import java.lang.reflect.ParameterizedType;
21 import java.lang.reflect.Type;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Optional;
26
27 import org.apache.ibatis.annotations.Flush;
28 import org.apache.ibatis.annotations.MapKey;
29 import org.apache.ibatis.cursor.Cursor;
30 import org.apache.ibatis.mapping.MappedStatement;
31 import org.apache.ibatis.mapping.SqlCommandType;
32 import org.apache.ibatis.mapping.StatementType;
33 import org.apache.ibatis.reflection.MetaObject;
34 import org.apache.ibatis.reflection.ParamNameResolver;
35 import org.apache.ibatis.reflection.TypeParameterResolver;
36 import org.apache.ibatis.session.Configuration;
37 import org.apache.ibatis.session.ResultHandler;
38 import org.apache.ibatis.session.RowBounds;
39 import org.apache.ibatis.session.SqlSession;
40
41
42
43
44
45
46
47 public class MapperMethod {
48
49 private final SqlCommand command;
50 private final MethodSignature method;
51
52 public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
53 this.command = new SqlCommand(config, mapperInterface, method);
54 this.method = new MethodSignature(config, mapperInterface, method);
55 }
56
57 public Object execute(SqlSession sqlSession, Object[] args) {
58 Object result;
59 switch (command.getType()) {
60 case INSERT: {
61 Object param = method.convertArgsToSqlCommandParam(args);
62 result = rowCountResult(sqlSession.insert(command.getName(), param));
63 break;
64 }
65 case UPDATE: {
66 Object param = method.convertArgsToSqlCommandParam(args);
67 result = rowCountResult(sqlSession.update(command.getName(), param));
68 break;
69 }
70 case DELETE: {
71 Object param = method.convertArgsToSqlCommandParam(args);
72 result = rowCountResult(sqlSession.delete(command.getName(), param));
73 break;
74 }
75 case SELECT:
76 if (method.returnsVoid() && method.hasResultHandler()) {
77 executeWithResultHandler(sqlSession, args);
78 result = null;
79 } else if (method.returnsMany()) {
80 result = executeForMany(sqlSession, args);
81 } else if (method.returnsMap()) {
82 result = executeForMap(sqlSession, args);
83 } else if (method.returnsCursor()) {
84 result = executeForCursor(sqlSession, args);
85 } else {
86 Object param = method.convertArgsToSqlCommandParam(args);
87 result = sqlSession.selectOne(command.getName(), param);
88 if (method.returnsOptional()
89 && (result == null || !method.getReturnType().equals(result.getClass()))) {
90 result = Optional.ofNullable(result);
91 }
92 }
93 break;
94 case FLUSH:
95 result = sqlSession.flushStatements();
96 break;
97 default:
98 throw new BindingException("Unknown execution method for: " + command.getName());
99 }
100 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
101 throw new BindingException("Mapper method '" + command.getName()
102 + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
103 }
104 return result;
105 }
106
107 private Object rowCountResult(int rowCount) {
108 final Object result;
109 if (method.returnsVoid()) {
110 result = null;
111 } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
112 result = rowCount;
113 } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
114 result = (long)rowCount;
115 } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
116 result = rowCount > 0;
117 } else {
118 throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
119 }
120 return result;
121 }
122
123 private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
124 MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
125 if (!StatementType.CALLABLE.equals(ms.getStatementType())
126 && void.class.equals(ms.getResultMaps().get(0).getType())) {
127 throw new BindingException("method " + command.getName()
128 + " needs either a @ResultMap annotation, a @ResultType annotation,"
129 + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
130 }
131 Object param = method.convertArgsToSqlCommandParam(args);
132 if (method.hasRowBounds()) {
133 RowBounds rowBounds = method.extractRowBounds(args);
134 sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
135 } else {
136 sqlSession.select(command.getName(), param, method.extractResultHandler(args));
137 }
138 }
139
140 private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
141 List<E> result;
142 Object param = method.convertArgsToSqlCommandParam(args);
143 if (method.hasRowBounds()) {
144 RowBounds rowBounds = method.extractRowBounds(args);
145 result = sqlSession.selectList(command.getName(), param, rowBounds);
146 } else {
147 result = sqlSession.selectList(command.getName(), param);
148 }
149
150 if (!method.getReturnType().isAssignableFrom(result.getClass())) {
151 if (method.getReturnType().isArray()) {
152 return convertToArray(result);
153 } else {
154 return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
155 }
156 }
157 return result;
158 }
159
160 private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
161 Cursor<T> result;
162 Object param = method.convertArgsToSqlCommandParam(args);
163 if (method.hasRowBounds()) {
164 RowBounds rowBounds = method.extractRowBounds(args);
165 result = sqlSession.selectCursor(command.getName(), param, rowBounds);
166 } else {
167 result = sqlSession.selectCursor(command.getName(), param);
168 }
169 return result;
170 }
171
172 private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
173 Object collection = config.getObjectFactory().create(method.getReturnType());
174 MetaObject metaObject = config.newMetaObject(collection);
175 metaObject.addAll(list);
176 return collection;
177 }
178
179 @SuppressWarnings("unchecked")
180 private <E> Object convertToArray(List<E> list) {
181 Class<?> arrayComponentType = method.getReturnType().getComponentType();
182 Object array = Array.newInstance(arrayComponentType, list.size());
183 if (arrayComponentType.isPrimitive()) {
184 for (int i = 0; i < list.size(); i++) {
185 Array.set(array, i, list.get(i));
186 }
187 return array;
188 } else {
189 return list.toArray((E[])array);
190 }
191 }
192
193 private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
194 Map<K, V> result;
195 Object param = method.convertArgsToSqlCommandParam(args);
196 if (method.hasRowBounds()) {
197 RowBounds rowBounds = method.extractRowBounds(args);
198 result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
199 } else {
200 result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
201 }
202 return result;
203 }
204
205 public static class ParamMap<V> extends HashMap<String, V> {
206
207 private static final long serialVersionUID = -2212268410512043556L;
208
209 @Override
210 public V get(Object key) {
211 if (!super.containsKey(key)) {
212 throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
213 }
214 return super.get(key);
215 }
216
217 }
218
219 public static class SqlCommand {
220
221 private final String name;
222 private final SqlCommandType type;
223
224 public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
225 final String methodName = method.getName();
226 final Class<?> declaringClass = method.getDeclaringClass();
227 MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
228 configuration);
229 if (ms == null) {
230 if (method.getAnnotation(Flush.class) != null) {
231 name = null;
232 type = SqlCommandType.FLUSH;
233 } else {
234 throw new BindingException("Invalid bound statement (not found): "
235 + mapperInterface.getName() + "." + methodName);
236 }
237 } else {
238 name = ms.getId();
239 type = ms.getSqlCommandType();
240 if (type == SqlCommandType.UNKNOWN) {
241 throw new BindingException("Unknown execution method for: " + name);
242 }
243 }
244 }
245
246 public String getName() {
247 return name;
248 }
249
250 public SqlCommandType getType() {
251 return type;
252 }
253
254 private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
255 Class<?> declaringClass, Configuration configuration) {
256 String statementId = mapperInterface.getName() + "." + methodName;
257 if (configuration.hasStatement(statementId)) {
258 return configuration.getMappedStatement(statementId);
259 } else if (mapperInterface.equals(declaringClass)) {
260 return null;
261 }
262 for (Class<?> superInterface : mapperInterface.getInterfaces()) {
263 if (declaringClass.isAssignableFrom(superInterface)) {
264 MappedStatement ms = resolveMappedStatement(superInterface, methodName,
265 declaringClass, configuration);
266 if (ms != null) {
267 return ms;
268 }
269 }
270 }
271 return null;
272 }
273 }
274
275 public static class MethodSignature {
276
277 private final boolean returnsMany;
278 private final boolean returnsMap;
279 private final boolean returnsVoid;
280 private final boolean returnsCursor;
281 private final boolean returnsOptional;
282 private final Class<?> returnType;
283 private final String mapKey;
284 private final Integer resultHandlerIndex;
285 private final Integer rowBoundsIndex;
286 private final ParamNameResolver paramNameResolver;
287
288 public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
289 Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
290 if (resolvedReturnType instanceof Class<?>) {
291 this.returnType = (Class<?>) resolvedReturnType;
292 } else if (resolvedReturnType instanceof ParameterizedType) {
293 this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
294 } else {
295 this.returnType = method.getReturnType();
296 }
297 this.returnsVoid = void.class.equals(this.returnType);
298 this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
299 this.returnsCursor = Cursor.class.equals(this.returnType);
300 this.returnsOptional = Optional.class.equals(this.returnType);
301 this.mapKey = getMapKey(method);
302 this.returnsMap = this.mapKey != null;
303 this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
304 this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
305 this.paramNameResolver = new ParamNameResolver(configuration, method);
306 }
307
308 public Object convertArgsToSqlCommandParam(Object[] args) {
309 return paramNameResolver.getNamedParams(args);
310 }
311
312 public boolean hasRowBounds() {
313 return rowBoundsIndex != null;
314 }
315
316 public RowBounds extractRowBounds(Object[] args) {
317 return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
318 }
319
320 public boolean hasResultHandler() {
321 return resultHandlerIndex != null;
322 }
323
324 public ResultHandler extractResultHandler(Object[] args) {
325 return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
326 }
327
328 public String getMapKey() {
329 return mapKey;
330 }
331
332 public Class<?> getReturnType() {
333 return returnType;
334 }
335
336 public boolean returnsMany() {
337 return returnsMany;
338 }
339
340 public boolean returnsMap() {
341 return returnsMap;
342 }
343
344 public boolean returnsVoid() {
345 return returnsVoid;
346 }
347
348 public boolean returnsCursor() {
349 return returnsCursor;
350 }
351
352
353
354
355
356
357 public boolean returnsOptional() {
358 return returnsOptional;
359 }
360
361 private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
362 Integer index = null;
363 final Class<?>[] argTypes = method.getParameterTypes();
364 for (int i = 0; i < argTypes.length; i++) {
365 if (paramType.isAssignableFrom(argTypes[i])) {
366 if (index == null) {
367 index = i;
368 } else {
369 throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
370 }
371 }
372 }
373 return index;
374 }
375
376 private String getMapKey(Method method) {
377 String mapKey = null;
378 if (Map.class.isAssignableFrom(method.getReturnType())) {
379 final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
380 if (mapKeyAnnotation != null) {
381 mapKey = mapKeyAnnotation.value();
382 }
383 }
384 return mapKey;
385 }
386 }
387
388 }