MapperMethod.java

  1. /*
  2.  *    Copyright 2009-2021 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.binding;

  17. import java.lang.reflect.Array;
  18. import java.lang.reflect.Method;
  19. import java.lang.reflect.ParameterizedType;
  20. import java.lang.reflect.Type;
  21. import java.util.HashMap;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.Optional;

  25. import org.apache.ibatis.annotations.Flush;
  26. import org.apache.ibatis.annotations.MapKey;
  27. import org.apache.ibatis.cursor.Cursor;
  28. import org.apache.ibatis.mapping.MappedStatement;
  29. import org.apache.ibatis.mapping.SqlCommandType;
  30. import org.apache.ibatis.mapping.StatementType;
  31. import org.apache.ibatis.reflection.MetaObject;
  32. import org.apache.ibatis.reflection.ParamNameResolver;
  33. import org.apache.ibatis.reflection.TypeParameterResolver;
  34. import org.apache.ibatis.session.Configuration;
  35. import org.apache.ibatis.session.ResultHandler;
  36. import org.apache.ibatis.session.RowBounds;
  37. import org.apache.ibatis.session.SqlSession;

  38. /**
  39.  * @author Clinton Begin
  40.  * @author Eduardo Macarron
  41.  * @author Lasse Voss
  42.  * @author Kazuki Shimizu
  43.  */
  44. public class MapperMethod {

  45.   private final SqlCommand command;
  46.   private final MethodSignature method;

  47.   public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
  48.     this.command = new SqlCommand(config, mapperInterface, method);
  49.     this.method = new MethodSignature(config, mapperInterface, method);
  50.   }

  51.   public Object execute(SqlSession sqlSession, Object[] args) {
  52.     Object result;
  53.     switch (command.getType()) {
  54.       case INSERT: {
  55.         Object param = method.convertArgsToSqlCommandParam(args);
  56.         result = rowCountResult(sqlSession.insert(command.getName(), param));
  57.         break;
  58.       }
  59.       case UPDATE: {
  60.         Object param = method.convertArgsToSqlCommandParam(args);
  61.         result = rowCountResult(sqlSession.update(command.getName(), param));
  62.         break;
  63.       }
  64.       case DELETE: {
  65.         Object param = method.convertArgsToSqlCommandParam(args);
  66.         result = rowCountResult(sqlSession.delete(command.getName(), param));
  67.         break;
  68.       }
  69.       case SELECT:
  70.         if (method.returnsVoid() && method.hasResultHandler()) {
  71.           executeWithResultHandler(sqlSession, args);
  72.           result = null;
  73.         } else if (method.returnsMany()) {
  74.           result = executeForMany(sqlSession, args);
  75.         } else if (method.returnsMap()) {
  76.           result = executeForMap(sqlSession, args);
  77.         } else if (method.returnsCursor()) {
  78.           result = executeForCursor(sqlSession, args);
  79.         } else {
  80.           Object param = method.convertArgsToSqlCommandParam(args);
  81.           result = sqlSession.selectOne(command.getName(), param);
  82.           if (method.returnsOptional()
  83.               && (result == null || !method.getReturnType().equals(result.getClass()))) {
  84.             result = Optional.ofNullable(result);
  85.           }
  86.         }
  87.         break;
  88.       case FLUSH:
  89.         result = sqlSession.flushStatements();
  90.         break;
  91.       default:
  92.         throw new BindingException("Unknown execution method for: " + command.getName());
  93.     }
  94.     if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
  95.       throw new BindingException("Mapper method '" + command.getName()
  96.           + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  97.     }
  98.     return result;
  99.   }

  100.   private Object rowCountResult(int rowCount) {
  101.     final Object result;
  102.     if (method.returnsVoid()) {
  103.       result = null;
  104.     } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
  105.       result = rowCount;
  106.     } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
  107.       result = (long) rowCount;
  108.     } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
  109.       result = rowCount > 0;
  110.     } else {
  111.       throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
  112.     }
  113.     return result;
  114.   }

  115.   private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
  116.     MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
  117.     if (!StatementType.CALLABLE.equals(ms.getStatementType())
  118.         && void.class.equals(ms.getResultMaps().get(0).getType())) {
  119.       throw new BindingException("method " + command.getName()
  120.           + " needs either a @ResultMap annotation, a @ResultType annotation,"
  121.           + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
  122.     }
  123.     Object param = method.convertArgsToSqlCommandParam(args);
  124.     if (method.hasRowBounds()) {
  125.       RowBounds rowBounds = method.extractRowBounds(args);
  126.       sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
  127.     } else {
  128.       sqlSession.select(command.getName(), param, method.extractResultHandler(args));
  129.     }
  130.   }

  131.   private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
  132.     List<E> result;
  133.     Object param = method.convertArgsToSqlCommandParam(args);
  134.     if (method.hasRowBounds()) {
  135.       RowBounds rowBounds = method.extractRowBounds(args);
  136.       result = sqlSession.selectList(command.getName(), param, rowBounds);
  137.     } else {
  138.       result = sqlSession.selectList(command.getName(), param);
  139.     }
  140.     // issue #510 Collections & arrays support
  141.     if (!method.getReturnType().isAssignableFrom(result.getClass())) {
  142.       if (method.getReturnType().isArray()) {
  143.         return convertToArray(result);
  144.       } else {
  145.         return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
  146.       }
  147.     }
  148.     return result;
  149.   }

  150.   private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
  151.     Cursor<T> result;
  152.     Object param = method.convertArgsToSqlCommandParam(args);
  153.     if (method.hasRowBounds()) {
  154.       RowBounds rowBounds = method.extractRowBounds(args);
  155.       result = sqlSession.selectCursor(command.getName(), param, rowBounds);
  156.     } else {
  157.       result = sqlSession.selectCursor(command.getName(), param);
  158.     }
  159.     return result;
  160.   }

  161.   private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
  162.     Object collection = config.getObjectFactory().create(method.getReturnType());
  163.     MetaObject metaObject = config.newMetaObject(collection);
  164.     metaObject.addAll(list);
  165.     return collection;
  166.   }

  167.   @SuppressWarnings("unchecked")
  168.   private <E> Object convertToArray(List<E> list) {
  169.     Class<?> arrayComponentType = method.getReturnType().getComponentType();
  170.     Object array = Array.newInstance(arrayComponentType, list.size());
  171.     if (arrayComponentType.isPrimitive()) {
  172.       for (int i = 0; i < list.size(); i++) {
  173.         Array.set(array, i, list.get(i));
  174.       }
  175.       return array;
  176.     } else {
  177.       return list.toArray((E[]) array);
  178.     }
  179.   }

  180.   private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
  181.     Map<K, V> result;
  182.     Object param = method.convertArgsToSqlCommandParam(args);
  183.     if (method.hasRowBounds()) {
  184.       RowBounds rowBounds = method.extractRowBounds(args);
  185.       result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
  186.     } else {
  187.       result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
  188.     }
  189.     return result;
  190.   }

  191.   public static class ParamMap<V> extends HashMap<String, V> {

  192.     private static final long serialVersionUID = -2212268410512043556L;

  193.     @Override
  194.     public V get(Object key) {
  195.       if (!super.containsKey(key)) {
  196.         throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
  197.       }
  198.       return super.get(key);
  199.     }

  200.   }

  201.   public static class SqlCommand {

  202.     private final String name;
  203.     private final SqlCommandType type;

  204.     public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
  205.       final String methodName = method.getName();
  206.       final Class<?> declaringClass = method.getDeclaringClass();
  207.       MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
  208.           configuration);
  209.       if (ms == null) {
  210.         if (method.getAnnotation(Flush.class) != null) {
  211.           name = null;
  212.           type = SqlCommandType.FLUSH;
  213.         } else {
  214.           throw new BindingException("Invalid bound statement (not found): "
  215.               + mapperInterface.getName() + "." + methodName);
  216.         }
  217.       } else {
  218.         name = ms.getId();
  219.         type = ms.getSqlCommandType();
  220.         if (type == SqlCommandType.UNKNOWN) {
  221.           throw new BindingException("Unknown execution method for: " + name);
  222.         }
  223.       }
  224.     }

  225.     public String getName() {
  226.       return name;
  227.     }

  228.     public SqlCommandType getType() {
  229.       return type;
  230.     }

  231.     private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
  232.         Class<?> declaringClass, Configuration configuration) {
  233.       String statementId = mapperInterface.getName() + "." + methodName;
  234.       if (configuration.hasStatement(statementId)) {
  235.         return configuration.getMappedStatement(statementId);
  236.       } else if (mapperInterface.equals(declaringClass)) {
  237.         return null;
  238.       }
  239.       for (Class<?> superInterface : mapperInterface.getInterfaces()) {
  240.         if (declaringClass.isAssignableFrom(superInterface)) {
  241.           MappedStatement ms = resolveMappedStatement(superInterface, methodName,
  242.               declaringClass, configuration);
  243.           if (ms != null) {
  244.             return ms;
  245.           }
  246.         }
  247.       }
  248.       return null;
  249.     }
  250.   }

  251.   public static class MethodSignature {

  252.     private final boolean returnsMany;
  253.     private final boolean returnsMap;
  254.     private final boolean returnsVoid;
  255.     private final boolean returnsCursor;
  256.     private final boolean returnsOptional;
  257.     private final Class<?> returnType;
  258.     private final String mapKey;
  259.     private final Integer resultHandlerIndex;
  260.     private final Integer rowBoundsIndex;
  261.     private final ParamNameResolver paramNameResolver;

  262.     public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
  263.       Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
  264.       if (resolvedReturnType instanceof Class<?>) {
  265.         this.returnType = (Class<?>) resolvedReturnType;
  266.       } else if (resolvedReturnType instanceof ParameterizedType) {
  267.         this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
  268.       } else {
  269.         this.returnType = method.getReturnType();
  270.       }
  271.       this.returnsVoid = void.class.equals(this.returnType);
  272.       this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
  273.       this.returnsCursor = Cursor.class.equals(this.returnType);
  274.       this.returnsOptional = Optional.class.equals(this.returnType);
  275.       this.mapKey = getMapKey(method);
  276.       this.returnsMap = this.mapKey != null;
  277.       this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
  278.       this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
  279.       this.paramNameResolver = new ParamNameResolver(configuration, method);
  280.     }

  281.     public Object convertArgsToSqlCommandParam(Object[] args) {
  282.       return paramNameResolver.getNamedParams(args);
  283.     }

  284.     public boolean hasRowBounds() {
  285.       return rowBoundsIndex != null;
  286.     }

  287.     public RowBounds extractRowBounds(Object[] args) {
  288.       return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
  289.     }

  290.     public boolean hasResultHandler() {
  291.       return resultHandlerIndex != null;
  292.     }

  293.     public ResultHandler extractResultHandler(Object[] args) {
  294.       return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
  295.     }

  296.     public Class<?> getReturnType() {
  297.       return returnType;
  298.     }

  299.     public boolean returnsMany() {
  300.       return returnsMany;
  301.     }

  302.     public boolean returnsMap() {
  303.       return returnsMap;
  304.     }

  305.     public boolean returnsVoid() {
  306.       return returnsVoid;
  307.     }

  308.     public boolean returnsCursor() {
  309.       return returnsCursor;
  310.     }

  311.     /**
  312.      * return whether return type is {@code java.util.Optional}.
  313.      *
  314.      * @return return {@code true}, if return type is {@code java.util.Optional}
  315.      * @since 3.5.0
  316.      */
  317.     public boolean returnsOptional() {
  318.       return returnsOptional;
  319.     }

  320.     private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
  321.       Integer index = null;
  322.       final Class<?>[] argTypes = method.getParameterTypes();
  323.       for (int i = 0; i < argTypes.length; i++) {
  324.         if (paramType.isAssignableFrom(argTypes[i])) {
  325.           if (index == null) {
  326.             index = i;
  327.           } else {
  328.             throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
  329.           }
  330.         }
  331.       }
  332.       return index;
  333.     }

  334.     public String getMapKey() {
  335.       return mapKey;
  336.     }

  337.     private String getMapKey(Method method) {
  338.       String mapKey = null;
  339.       if (Map.class.isAssignableFrom(method.getReturnType())) {
  340.         final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
  341.         if (mapKeyAnnotation != null) {
  342.           mapKey = mapKeyAnnotation.value();
  343.         }
  344.       }
  345.       return mapKey;
  346.     }
  347.   }

  348. }