DefaultResultSetHandler.java

  1. /*
  2.  *    Copyright 2009-2024 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.  *       https://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.executor.resultset;

  17. import java.lang.reflect.Constructor;
  18. import java.lang.reflect.Parameter;
  19. import java.sql.CallableStatement;
  20. import java.sql.ResultSet;
  21. import java.sql.SQLException;
  22. import java.sql.Statement;
  23. import java.text.MessageFormat;
  24. import java.util.ArrayList;
  25. import java.util.Arrays;
  26. import java.util.HashMap;
  27. import java.util.HashSet;
  28. import java.util.List;
  29. import java.util.Locale;
  30. import java.util.Map;
  31. import java.util.Optional;
  32. import java.util.Set;

  33. import org.apache.ibatis.annotations.AutomapConstructor;
  34. import org.apache.ibatis.annotations.Param;
  35. import org.apache.ibatis.binding.MapperMethod.ParamMap;
  36. import org.apache.ibatis.cache.CacheKey;
  37. import org.apache.ibatis.cursor.Cursor;
  38. import org.apache.ibatis.cursor.defaults.DefaultCursor;
  39. import org.apache.ibatis.executor.ErrorContext;
  40. import org.apache.ibatis.executor.Executor;
  41. import org.apache.ibatis.executor.ExecutorException;
  42. import org.apache.ibatis.executor.loader.ResultLoader;
  43. import org.apache.ibatis.executor.loader.ResultLoaderMap;
  44. import org.apache.ibatis.executor.parameter.ParameterHandler;
  45. import org.apache.ibatis.executor.result.DefaultResultContext;
  46. import org.apache.ibatis.executor.result.DefaultResultHandler;
  47. import org.apache.ibatis.executor.result.ResultMapException;
  48. import org.apache.ibatis.mapping.BoundSql;
  49. import org.apache.ibatis.mapping.Discriminator;
  50. import org.apache.ibatis.mapping.MappedStatement;
  51. import org.apache.ibatis.mapping.ParameterMapping;
  52. import org.apache.ibatis.mapping.ParameterMode;
  53. import org.apache.ibatis.mapping.ResultMap;
  54. import org.apache.ibatis.mapping.ResultMapping;
  55. import org.apache.ibatis.reflection.MetaClass;
  56. import org.apache.ibatis.reflection.MetaObject;
  57. import org.apache.ibatis.reflection.ReflectorFactory;
  58. import org.apache.ibatis.reflection.factory.ObjectFactory;
  59. import org.apache.ibatis.session.AutoMappingBehavior;
  60. import org.apache.ibatis.session.Configuration;
  61. import org.apache.ibatis.session.ResultContext;
  62. import org.apache.ibatis.session.ResultHandler;
  63. import org.apache.ibatis.session.RowBounds;
  64. import org.apache.ibatis.type.JdbcType;
  65. import org.apache.ibatis.type.TypeHandler;
  66. import org.apache.ibatis.type.TypeHandlerRegistry;
  67. import org.apache.ibatis.util.MapUtil;

  68. /**
  69.  * @author Clinton Begin
  70.  * @author Eduardo Macarron
  71.  * @author Iwao AVE!
  72.  * @author Kazuki Shimizu
  73.  */
  74. public class DefaultResultSetHandler implements ResultSetHandler {

  75.   private static final Object DEFERRED = new Object();

  76.   private final Executor executor;
  77.   private final Configuration configuration;
  78.   private final MappedStatement mappedStatement;
  79.   private final RowBounds rowBounds;
  80.   private final ParameterHandler parameterHandler;
  81.   private final ResultHandler<?> resultHandler;
  82.   private final BoundSql boundSql;
  83.   private final TypeHandlerRegistry typeHandlerRegistry;
  84.   private final ObjectFactory objectFactory;
  85.   private final ReflectorFactory reflectorFactory;

  86.   // nested resultmaps
  87.   private final Map<CacheKey, Object> nestedResultObjects = new HashMap<>();
  88.   private final Map<String, Object> ancestorObjects = new HashMap<>();
  89.   private Object previousRowValue;

  90.   // multiple resultsets
  91.   private final Map<String, ResultMapping> nextResultMaps = new HashMap<>();
  92.   private final Map<CacheKey, List<PendingRelation>> pendingRelations = new HashMap<>();

  93.   // Cached Automappings
  94.   private final Map<String, List<UnMappedColumnAutoMapping>> autoMappingsCache = new HashMap<>();
  95.   private final Map<String, List<String>> constructorAutoMappingColumns = new HashMap<>();

  96.   // temporary marking flag that indicate using constructor mapping (use field to reduce memory usage)
  97.   private boolean useConstructorMappings;

  98.   private static class PendingRelation {
  99.     public MetaObject metaObject;
  100.     public ResultMapping propertyMapping;
  101.   }

  102.   private static class UnMappedColumnAutoMapping {
  103.     private final String column;
  104.     private final String property;
  105.     private final TypeHandler<?> typeHandler;
  106.     private final boolean primitive;

  107.     public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
  108.       this.column = column;
  109.       this.property = property;
  110.       this.typeHandler = typeHandler;
  111.       this.primitive = primitive;
  112.     }
  113.   }

  114.   public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler,
  115.       ResultHandler<?> resultHandler, BoundSql boundSql, RowBounds rowBounds) {
  116.     this.executor = executor;
  117.     this.configuration = mappedStatement.getConfiguration();
  118.     this.mappedStatement = mappedStatement;
  119.     this.rowBounds = rowBounds;
  120.     this.parameterHandler = parameterHandler;
  121.     this.boundSql = boundSql;
  122.     this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
  123.     this.objectFactory = configuration.getObjectFactory();
  124.     this.reflectorFactory = configuration.getReflectorFactory();
  125.     this.resultHandler = resultHandler;
  126.   }

  127.   //
  128.   // HANDLE OUTPUT PARAMETER
  129.   //

  130.   @Override
  131.   public void handleOutputParameters(CallableStatement cs) throws SQLException {
  132.     final Object parameterObject = parameterHandler.getParameterObject();
  133.     final MetaObject metaParam = configuration.newMetaObject(parameterObject);
  134.     final List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  135.     for (int i = 0; i < parameterMappings.size(); i++) {
  136.       final ParameterMapping parameterMapping = parameterMappings.get(i);
  137.       if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) {
  138.         if (ResultSet.class.equals(parameterMapping.getJavaType())) {
  139.           handleRefCursorOutputParameter((ResultSet) cs.getObject(i + 1), parameterMapping, metaParam);
  140.         } else {
  141.           final TypeHandler<?> typeHandler = parameterMapping.getTypeHandler();
  142.           metaParam.setValue(parameterMapping.getProperty(), typeHandler.getResult(cs, i + 1));
  143.         }
  144.       }
  145.     }
  146.   }

  147.   private void handleRefCursorOutputParameter(ResultSet rs, ParameterMapping parameterMapping, MetaObject metaParam)
  148.       throws SQLException {
  149.     if (rs == null) {
  150.       return;
  151.     }
  152.     try {
  153.       final String resultMapId = parameterMapping.getResultMapId();
  154.       final ResultMap resultMap = configuration.getResultMap(resultMapId);
  155.       final ResultSetWrapper rsw = new ResultSetWrapper(rs, configuration);
  156.       if (this.resultHandler == null) {
  157.         final DefaultResultHandler resultHandler = new DefaultResultHandler(objectFactory);
  158.         handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
  159.         metaParam.setValue(parameterMapping.getProperty(), resultHandler.getResultList());
  160.       } else {
  161.         handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
  162.       }
  163.     } finally {
  164.       // issue #228 (close resultsets)
  165.       closeResultSet(rs);
  166.     }
  167.   }

  168.   //
  169.   // HANDLE RESULT SETS
  170.   //
  171.   @Override
  172.   public List<Object> handleResultSets(Statement stmt) throws SQLException {
  173.     ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

  174.     final List<Object> multipleResults = new ArrayList<>();

  175.     int resultSetCount = 0;
  176.     ResultSetWrapper rsw = getFirstResultSet(stmt);

  177.     List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  178.     int resultMapCount = resultMaps.size();
  179.     validateResultMapsCount(rsw, resultMapCount);
  180.     while (rsw != null && resultMapCount > resultSetCount) {
  181.       ResultMap resultMap = resultMaps.get(resultSetCount);
  182.       handleResultSet(rsw, resultMap, multipleResults, null);
  183.       rsw = getNextResultSet(stmt);
  184.       cleanUpAfterHandlingResultSet();
  185.       resultSetCount++;
  186.     }

  187.     String[] resultSets = mappedStatement.getResultSets();
  188.     if (resultSets != null) {
  189.       while (rsw != null && resultSetCount < resultSets.length) {
  190.         ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
  191.         if (parentMapping != null) {
  192.           String nestedResultMapId = parentMapping.getNestedResultMapId();
  193.           ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
  194.           handleResultSet(rsw, resultMap, null, parentMapping);
  195.         }
  196.         rsw = getNextResultSet(stmt);
  197.         cleanUpAfterHandlingResultSet();
  198.         resultSetCount++;
  199.       }
  200.     }

  201.     return collapseSingleResultList(multipleResults);
  202.   }

  203.   @Override
  204.   public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException {
  205.     ErrorContext.instance().activity("handling cursor results").object(mappedStatement.getId());

  206.     ResultSetWrapper rsw = getFirstResultSet(stmt);

  207.     List<ResultMap> resultMaps = mappedStatement.getResultMaps();

  208.     int resultMapCount = resultMaps.size();
  209.     validateResultMapsCount(rsw, resultMapCount);
  210.     if (resultMapCount != 1) {
  211.       throw new ExecutorException("Cursor results cannot be mapped to multiple resultMaps");
  212.     }

  213.     ResultMap resultMap = resultMaps.get(0);
  214.     return new DefaultCursor<>(this, resultMap, rsw, rowBounds);
  215.   }

  216.   private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
  217.     ResultSet rs = stmt.getResultSet();
  218.     while (rs == null) {
  219.       // move forward to get the first resultset in case the driver
  220.       // doesn't return the resultset as the first result (HSQLDB)
  221.       if (stmt.getMoreResults()) {
  222.         rs = stmt.getResultSet();
  223.       } else if (stmt.getUpdateCount() == -1) {
  224.         // no more results. Must be no resultset
  225.         break;
  226.       }
  227.     }
  228.     return rs != null ? new ResultSetWrapper(rs, configuration) : null;
  229.   }

  230.   private ResultSetWrapper getNextResultSet(Statement stmt) {
  231.     // Making this method tolerant of bad JDBC drivers
  232.     try {
  233.       if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
  234.         // Crazy Standard JDBC way of determining if there are more results
  235.         // DO NOT try to 'improve' the condition even if IDE tells you to!
  236.         // It's important that getUpdateCount() is called here.
  237.         if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
  238.           ResultSet rs = stmt.getResultSet();
  239.           if (rs == null) {
  240.             return getNextResultSet(stmt);
  241.           } else {
  242.             return new ResultSetWrapper(rs, configuration);
  243.           }
  244.         }
  245.       }
  246.     } catch (Exception e) {
  247.       // Intentionally ignored.
  248.     }
  249.     return null;
  250.   }

  251.   private void closeResultSet(ResultSet rs) {
  252.     try {
  253.       if (rs != null) {
  254.         rs.close();
  255.       }
  256.     } catch (SQLException e) {
  257.       // ignore
  258.     }
  259.   }

  260.   private void cleanUpAfterHandlingResultSet() {
  261.     nestedResultObjects.clear();
  262.   }

  263.   private void validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount) {
  264.     if (rsw != null && resultMapCount < 1) {
  265.       throw new ExecutorException(
  266.           "A query was run and no Result Maps were found for the Mapped Statement '" + mappedStatement.getId()
  267.               + "'. 'resultType' or 'resultMap' must be specified when there is no corresponding method.");
  268.     }
  269.   }

  270.   private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults,
  271.       ResultMapping parentMapping) throws SQLException {
  272.     try {
  273.       if (parentMapping != null) {
  274.         handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
  275.       } else if (resultHandler == null) {
  276.         DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
  277.         handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
  278.         multipleResults.add(defaultResultHandler.getResultList());
  279.       } else {
  280.         handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
  281.       }
  282.     } finally {
  283.       // issue #228 (close resultsets)
  284.       closeResultSet(rsw.getResultSet());
  285.     }
  286.   }

  287.   @SuppressWarnings("unchecked")
  288.   private List<Object> collapseSingleResultList(List<Object> multipleResults) {
  289.     return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
  290.   }

  291.   //
  292.   // HANDLE ROWS FOR SIMPLE RESULTMAP
  293.   //

  294.   public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
  295.       RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  296.     if (resultMap.hasNestedResultMaps()) {
  297.       ensureNoRowBounds();
  298.       checkResultHandler();
  299.       handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  300.     } else {
  301.       handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  302.     }
  303.   }

  304.   private void ensureNoRowBounds() {
  305.     if (configuration.isSafeRowBoundsEnabled() && rowBounds != null
  306.         && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) {
  307.       throw new ExecutorException(
  308.           "Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. "
  309.               + "Use safeRowBoundsEnabled=false setting to bypass this check.");
  310.     }
  311.   }

  312.   protected void checkResultHandler() {
  313.     if (resultHandler != null && configuration.isSafeResultHandlerEnabled() && !mappedStatement.isResultOrdered()) {
  314.       throw new ExecutorException(
  315.           "Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler. "
  316.               + "Use safeResultHandlerEnabled=false setting to bypass this check "
  317.               + "or ensure your statement returns ordered data and set resultOrdered=true on it.");
  318.     }
  319.   }

  320.   private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
  321.       ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  322.     DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
  323.     ResultSet resultSet = rsw.getResultSet();
  324.     skipRows(resultSet, rowBounds);
  325.     while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
  326.       ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
  327.       Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
  328.       storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  329.     }
  330.   }

  331.   private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue,
  332.       ResultMapping parentMapping, ResultSet rs) throws SQLException {
  333.     if (parentMapping != null) {
  334.       linkToParents(rs, parentMapping, rowValue);
  335.     } else {
  336.       callResultHandler(resultHandler, resultContext, rowValue);
  337.     }
  338.   }

  339.   @SuppressWarnings("unchecked" /* because ResultHandler<?> is always ResultHandler<Object> */)
  340.   private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext,
  341.       Object rowValue) {
  342.     resultContext.nextResultObject(rowValue);
  343.     ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
  344.   }

  345.   private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
  346.     return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
  347.   }

  348.   private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
  349.     if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
  350.       if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
  351.         rs.absolute(rowBounds.getOffset());
  352.       }
  353.     } else {
  354.       for (int i = 0; i < rowBounds.getOffset(); i++) {
  355.         if (!rs.next()) {
  356.           break;
  357.         }
  358.       }
  359.     }
  360.   }

  361.   //
  362.   // GET VALUE FROM ROW FOR SIMPLE RESULT MAP
  363.   //

  364.   private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
  365.     final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  366.     Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
  367.     if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
  368.       final MetaObject metaObject = configuration.newMetaObject(rowValue);
  369.       boolean foundValues = this.useConstructorMappings;
  370.       if (shouldApplyAutomaticMappings(resultMap, false)) {
  371.         foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
  372.       }
  373.       foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
  374.       foundValues = lazyLoader.size() > 0 || foundValues;
  375.       rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
  376.     }
  377.     return rowValue;
  378.   }

  379.   //
  380.   // GET VALUE FROM ROW FOR NESTED RESULT MAP
  381.   //

  382.   private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix,
  383.       Object partialObject) throws SQLException {
  384.     final String resultMapId = resultMap.getId();
  385.     Object rowValue = partialObject;
  386.     if (rowValue != null) {
  387.       final MetaObject metaObject = configuration.newMetaObject(rowValue);
  388.       putAncestor(rowValue, resultMapId);
  389.       applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
  390.       ancestorObjects.remove(resultMapId);
  391.     } else {
  392.       final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  393.       rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
  394.       if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
  395.         final MetaObject metaObject = configuration.newMetaObject(rowValue);
  396.         boolean foundValues = this.useConstructorMappings;
  397.         if (shouldApplyAutomaticMappings(resultMap, true)) {
  398.           foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
  399.         }
  400.         foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
  401.         putAncestor(rowValue, resultMapId);
  402.         foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true)
  403.             || foundValues;
  404.         ancestorObjects.remove(resultMapId);
  405.         foundValues = lazyLoader.size() > 0 || foundValues;
  406.         rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
  407.       }
  408.       if (combinedKey != CacheKey.NULL_CACHE_KEY) {
  409.         nestedResultObjects.put(combinedKey, rowValue);
  410.       }
  411.     }
  412.     return rowValue;
  413.   }

  414.   private void putAncestor(Object resultObject, String resultMapId) {
  415.     ancestorObjects.put(resultMapId, resultObject);
  416.   }

  417.   private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
  418.     if (resultMap.getAutoMapping() != null) {
  419.       return resultMap.getAutoMapping();
  420.     }
  421.     if (isNested) {
  422.       return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
  423.     } else {
  424.       return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
  425.     }
  426.   }

  427.   //
  428.   // PROPERTY MAPPINGS
  429.   //

  430.   private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
  431.       ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
  432.     final Set<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
  433.     boolean foundValues = false;
  434.     final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  435.     for (ResultMapping propertyMapping : propertyMappings) {
  436.       String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
  437.       if (propertyMapping.getNestedResultMapId() != null) {
  438.         // the user added a column attribute to a nested result map, ignore it
  439.         column = null;
  440.       }
  441.       if (propertyMapping.isCompositeResult()
  442.           || column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))
  443.           || propertyMapping.getResultSet() != null) {
  444.         Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader,
  445.             columnPrefix);
  446.         // issue #541 make property optional
  447.         final String property = propertyMapping.getProperty();
  448.         if (property == null) {
  449.           continue;
  450.         }
  451.         if (value == DEFERRED) {
  452.           foundValues = true;
  453.           continue;
  454.         }
  455.         if (value != null) {
  456.           foundValues = true;
  457.         }
  458.         if (value != null
  459.             || configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()) {
  460.           // gcode issue #377, call setter on nulls (value is not 'found')
  461.           metaObject.setValue(property, value);
  462.         }
  463.       }
  464.     }
  465.     return foundValues;
  466.   }

  467.   private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
  468.       ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
  469.     if (propertyMapping.getNestedQueryId() != null) {
  470.       return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
  471.     }
  472.     if (propertyMapping.getResultSet() != null) {
  473.       addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
  474.       return DEFERRED;
  475.     } else {
  476.       final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
  477.       final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
  478.       return typeHandler.getResult(rs, column);
  479.     }
  480.   }

  481.   private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap,
  482.       MetaObject metaObject, String columnPrefix) throws SQLException {
  483.     final String mapKey = resultMap.getId() + ":" + columnPrefix;
  484.     List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
  485.     if (autoMapping == null) {
  486.       autoMapping = new ArrayList<>();
  487.       final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
  488.       // Remove the entry to release the memory
  489.       List<String> mappedInConstructorAutoMapping = constructorAutoMappingColumns.remove(mapKey);
  490.       if (mappedInConstructorAutoMapping != null) {
  491.         unmappedColumnNames.removeAll(mappedInConstructorAutoMapping);
  492.       }
  493.       for (String columnName : unmappedColumnNames) {
  494.         String propertyName = columnName;
  495.         if (columnPrefix != null && !columnPrefix.isEmpty()) {
  496.           // When columnPrefix is specified,
  497.           // ignore columns without the prefix.
  498.           if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
  499.             continue;
  500.           }
  501.           propertyName = columnName.substring(columnPrefix.length());
  502.         }
  503.         final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
  504.         if (property != null && metaObject.hasSetter(property)) {
  505.           if (resultMap.getMappedProperties().contains(property)) {
  506.             continue;
  507.           }
  508.           final Class<?> propertyType = metaObject.getSetterType(property);
  509.           if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
  510.             final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
  511.             autoMapping
  512.                 .add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
  513.           } else {
  514.             configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property,
  515.                 propertyType);
  516.           }
  517.         } else {
  518.           configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName,
  519.               property != null ? property : propertyName, null);
  520.         }
  521.       }
  522.       autoMappingsCache.put(mapKey, autoMapping);
  523.     }
  524.     return autoMapping;
  525.   }

  526.   private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
  527.       String columnPrefix) throws SQLException {
  528.     List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
  529.     boolean foundValues = false;
  530.     if (!autoMapping.isEmpty()) {
  531.       for (UnMappedColumnAutoMapping mapping : autoMapping) {
  532.         final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
  533.         if (value != null) {
  534.           foundValues = true;
  535.         }
  536.         if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) {
  537.           // gcode issue #377, call setter on nulls (value is not 'found')
  538.           metaObject.setValue(mapping.property, value);
  539.         }
  540.       }
  541.     }
  542.     return foundValues;
  543.   }

  544.   // MULTIPLE RESULT SETS

  545.   private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException {
  546.     CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(),
  547.         parentMapping.getForeignColumn());
  548.     List<PendingRelation> parents = pendingRelations.get(parentKey);
  549.     if (parents != null) {
  550.       for (PendingRelation parent : parents) {
  551.         if (parent != null && rowValue != null) {
  552.           linkObjects(parent.metaObject, parent.propertyMapping, rowValue);
  553.         }
  554.       }
  555.     }
  556.   }

  557.   private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, ResultMapping parentMapping)
  558.       throws SQLException {
  559.     CacheKey cacheKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(),
  560.         parentMapping.getColumn());
  561.     PendingRelation deferLoad = new PendingRelation();
  562.     deferLoad.metaObject = metaResultObject;
  563.     deferLoad.propertyMapping = parentMapping;
  564.     List<PendingRelation> relations = MapUtil.computeIfAbsent(pendingRelations, cacheKey, k -> new ArrayList<>());
  565.     // issue #255
  566.     relations.add(deferLoad);
  567.     ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet());
  568.     if (previous == null) {
  569.       nextResultMaps.put(parentMapping.getResultSet(), parentMapping);
  570.     } else if (!previous.equals(parentMapping)) {
  571.       throw new ExecutorException("Two different properties are mapped to the same resultSet");
  572.     }
  573.   }

  574.   private CacheKey createKeyForMultipleResults(ResultSet rs, ResultMapping resultMapping, String names, String columns)
  575.       throws SQLException {
  576.     CacheKey cacheKey = new CacheKey();
  577.     cacheKey.update(resultMapping);
  578.     if (columns != null && names != null) {
  579.       String[] columnsArray = columns.split(",");
  580.       String[] namesArray = names.split(",");
  581.       for (int i = 0; i < columnsArray.length; i++) {
  582.         Object value = rs.getString(columnsArray[i]);
  583.         if (value != null) {
  584.           cacheKey.update(namesArray[i]);
  585.           cacheKey.update(value);
  586.         }
  587.       }
  588.     }
  589.     return cacheKey;
  590.   }

  591.   //
  592.   // INSTANTIATION & CONSTRUCTOR MAPPING
  593.   //

  594.   private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,
  595.       String columnPrefix) throws SQLException {
  596.     this.useConstructorMappings = false; // reset previous mapping result
  597.     final List<Class<?>> constructorArgTypes = new ArrayList<>();
  598.     final List<Object> constructorArgs = new ArrayList<>();
  599.     Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
  600.     if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
  601.       final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  602.       for (ResultMapping propertyMapping : propertyMappings) {
  603.         // issue gcode #109 && issue #149
  604.         if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
  605.           resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration,
  606.               objectFactory, constructorArgTypes, constructorArgs);
  607.           break;
  608.         }
  609.       }
  610.     }
  611.     this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
  612.     return resultObject;
  613.   }

  614.   private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
  615.       List<Object> constructorArgs, String columnPrefix) throws SQLException {
  616.     final Class<?> resultType = resultMap.getType();
  617.     final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
  618.     final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
  619.     if (hasTypeHandlerForResultObject(rsw, resultType)) {
  620.       return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
  621.     }
  622.     if (!constructorMappings.isEmpty()) {
  623.       return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs,
  624.           columnPrefix);
  625.     } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
  626.       return objectFactory.create(resultType);
  627.     } else if (shouldApplyAutomaticMappings(resultMap, false)) {
  628.       return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes,
  629.           constructorArgs);
  630.     }
  631.     throw new ExecutorException("Do not know how to create an instance of " + resultType);
  632.   }

  633.   Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType,
  634.       List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
  635.       String columnPrefix) {
  636.     boolean foundValues = false;
  637.     for (ResultMapping constructorMapping : constructorMappings) {
  638.       final Class<?> parameterType = constructorMapping.getJavaType();
  639.       final String column = constructorMapping.getColumn();
  640.       final Object value;
  641.       try {
  642.         if (constructorMapping.getNestedQueryId() != null) {
  643.           value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
  644.         } else if (constructorMapping.getNestedResultMapId() != null) {
  645.           String constructorColumnPrefix = getColumnPrefix(columnPrefix, constructorMapping);
  646.           final ResultMap resultMap = resolveDiscriminatedResultMap(rsw.getResultSet(),
  647.               configuration.getResultMap(constructorMapping.getNestedResultMapId()), constructorColumnPrefix);
  648.           value = getRowValue(rsw, resultMap, constructorColumnPrefix);
  649.         } else {
  650.           final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
  651.           value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
  652.         }
  653.       } catch (ResultMapException | SQLException e) {
  654.         throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
  655.       }
  656.       constructorArgTypes.add(parameterType);
  657.       constructorArgs.add(value);
  658.       foundValues = value != null || foundValues;
  659.     }
  660.     return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
  661.   }

  662.   private Object createByConstructorSignature(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
  663.       Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
  664.     return applyConstructorAutomapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs,
  665.         findConstructorForAutomapping(resultType, rsw).orElseThrow(() -> new ExecutorException(
  666.             "No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames())));
  667.   }

  668.   private Optional<Constructor<?>> findConstructorForAutomapping(final Class<?> resultType, ResultSetWrapper rsw) {
  669.     Constructor<?>[] constructors = resultType.getDeclaredConstructors();
  670.     if (constructors.length == 1) {
  671.       return Optional.of(constructors[0]);
  672.     }
  673.     Optional<Constructor<?>> annotated = Arrays.stream(constructors)
  674.         .filter(x -> x.isAnnotationPresent(AutomapConstructor.class)).reduce((x, y) -> {
  675.           throw new ExecutorException("@AutomapConstructor should be used in only one constructor.");
  676.         });
  677.     if (annotated.isPresent()) {
  678.       return annotated;
  679.     }
  680.     if (configuration.isArgNameBasedConstructorAutoMapping()) {
  681.       // Finding-best-match type implementation is possible,
  682.       // but using @AutomapConstructor seems sufficient.
  683.       throw new ExecutorException(MessageFormat.format(
  684.           "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.",
  685.           resultType.getName()));
  686.     } else {
  687.       return Arrays.stream(constructors).filter(x -> findUsableConstructorByArgTypes(x, rsw.getJdbcTypes())).findAny();
  688.     }
  689.   }

  690.   private boolean findUsableConstructorByArgTypes(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) {
  691.     final Class<?>[] parameterTypes = constructor.getParameterTypes();
  692.     if (parameterTypes.length != jdbcTypes.size()) {
  693.       return false;
  694.     }
  695.     for (int i = 0; i < parameterTypes.length; i++) {
  696.       if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) {
  697.         return false;
  698.       }
  699.     }
  700.     return true;
  701.   }

  702.   private Object applyConstructorAutomapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
  703.       Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor)
  704.       throws SQLException {
  705.     boolean foundValues = false;
  706.     if (configuration.isArgNameBasedConstructorAutoMapping()) {
  707.       foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, constructorArgTypes,
  708.           constructorArgs, constructor, foundValues);
  709.     } else {
  710.       foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor,
  711.           foundValues);
  712.     }
  713.     return foundValues || configuration.isReturnInstanceForEmptyRow()
  714.         ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
  715.   }

  716.   private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,
  717.       List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {
  718.     Class<?>[] parameterTypes = constructor.getParameterTypes();

  719.     if (parameterTypes.length > rsw.getClassNames().size()) {
  720.       throw new ExecutorException(MessageFormat.format(
  721.           "Constructor auto-mapping of ''{0}'' failed. The constructor takes ''{1}'' arguments, but there are only ''{2}'' columns in the result set.",
  722.           constructor, parameterTypes.length, rsw.getClassNames().size()));
  723.     }

  724.     for (int i = 0; i < parameterTypes.length; i++) {
  725.       Class<?> parameterType = parameterTypes[i];
  726.       String columnName = rsw.getColumnNames().get(i);
  727.       TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
  728.       Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
  729.       constructorArgTypes.add(parameterType);
  730.       constructorArgs.add(value);
  731.       foundValues = value != null || foundValues;
  732.     }
  733.     return foundValues;
  734.   }

  735.   private boolean applyArgNameBasedConstructorAutoMapping(ResultSetWrapper rsw, ResultMap resultMap,
  736.       String columnPrefix, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor,
  737.       boolean foundValues) throws SQLException {
  738.     List<String> missingArgs = null;
  739.     Parameter[] params = constructor.getParameters();
  740.     for (Parameter param : params) {
  741.       boolean columnNotFound = true;
  742.       Param paramAnno = param.getAnnotation(Param.class);
  743.       String paramName = paramAnno == null ? param.getName() : paramAnno.value();
  744.       for (String columnName : rsw.getColumnNames()) {
  745.         if (columnMatchesParam(columnName, paramName, columnPrefix)) {
  746.           Class<?> paramType = param.getType();
  747.           TypeHandler<?> typeHandler = rsw.getTypeHandler(paramType, columnName);
  748.           Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
  749.           constructorArgTypes.add(paramType);
  750.           constructorArgs.add(value);
  751.           final String mapKey = resultMap.getId() + ":" + columnPrefix;
  752.           if (!autoMappingsCache.containsKey(mapKey)) {
  753.             MapUtil.computeIfAbsent(constructorAutoMappingColumns, mapKey, k -> new ArrayList<>()).add(columnName);
  754.           }
  755.           columnNotFound = false;
  756.           foundValues = value != null || foundValues;
  757.         }
  758.       }
  759.       if (columnNotFound) {
  760.         if (missingArgs == null) {
  761.           missingArgs = new ArrayList<>();
  762.         }
  763.         missingArgs.add(paramName);
  764.       }
  765.     }
  766.     if (foundValues && constructorArgs.size() < params.length) {
  767.       throw new ExecutorException(MessageFormat.format(
  768.           "Constructor auto-mapping of ''{1}'' failed " + "because ''{0}'' were not found in the result set; "
  769.               + "Available columns are ''{2}'' and mapUnderscoreToCamelCase is ''{3}''.",
  770.           missingArgs, constructor, rsw.getColumnNames(), configuration.isMapUnderscoreToCamelCase()));
  771.     }
  772.     return foundValues;
  773.   }

  774.   private boolean columnMatchesParam(String columnName, String paramName, String columnPrefix) {
  775.     if (columnPrefix != null) {
  776.       if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
  777.         return false;
  778.       }
  779.       columnName = columnName.substring(columnPrefix.length());
  780.     }
  781.     return paramName
  782.         .equalsIgnoreCase(configuration.isMapUnderscoreToCamelCase() ? columnName.replace("_", "") : columnName);
  783.   }

  784.   private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
  785.       throws SQLException {
  786.     final Class<?> resultType = resultMap.getType();
  787.     final String columnName;
  788.     if (!resultMap.getResultMappings().isEmpty()) {
  789.       final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
  790.       final ResultMapping mapping = resultMappingList.get(0);
  791.       columnName = prependPrefix(mapping.getColumn(), columnPrefix);
  792.     } else {
  793.       columnName = rsw.getColumnNames().get(0);
  794.     }
  795.     final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
  796.     return typeHandler.getResult(rsw.getResultSet(), columnName);
  797.   }

  798.   //
  799.   // NESTED QUERY
  800.   //

  801.   private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)
  802.       throws SQLException {
  803.     final String nestedQueryId = constructorMapping.getNestedQueryId();
  804.     final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
  805.     final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
  806.     final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping,
  807.         nestedQueryParameterType, columnPrefix);
  808.     Object value = null;
  809.     if (nestedQueryParameterObject != null) {
  810.       final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
  811.       final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
  812.           nestedBoundSql);
  813.       final Class<?> targetType = constructorMapping.getJavaType();
  814.       final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
  815.           nestedQueryParameterObject, targetType, key, nestedBoundSql);
  816.       value = resultLoader.loadResult();
  817.     }
  818.     return value;
  819.   }

  820.   private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
  821.       ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
  822.     final String nestedQueryId = propertyMapping.getNestedQueryId();
  823.     final String property = propertyMapping.getProperty();
  824.     final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
  825.     final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
  826.     final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping,
  827.         nestedQueryParameterType, columnPrefix);
  828.     Object value = null;
  829.     if (nestedQueryParameterObject != null) {
  830.       final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
  831.       final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
  832.           nestedBoundSql);
  833.       final Class<?> targetType = propertyMapping.getJavaType();
  834.       if (executor.isCached(nestedQuery, key)) {
  835.         executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
  836.         value = DEFERRED;
  837.       } else {
  838.         final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
  839.             nestedQueryParameterObject, targetType, key, nestedBoundSql);
  840.         if (propertyMapping.isLazy()) {
  841.           lazyLoader.addLoader(property, metaResultObject, resultLoader);
  842.           value = DEFERRED;
  843.         } else {
  844.           value = resultLoader.loadResult();
  845.         }
  846.       }
  847.     }
  848.     return value;
  849.   }

  850.   private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
  851.       String columnPrefix) throws SQLException {
  852.     if (resultMapping.isCompositeResult()) {
  853.       return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix);
  854.     }
  855.     return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix);
  856.   }

  857.   private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
  858.       String columnPrefix) throws SQLException {
  859.     final TypeHandler<?> typeHandler;
  860.     if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
  861.       typeHandler = typeHandlerRegistry.getTypeHandler(parameterType);
  862.     } else {
  863.       typeHandler = typeHandlerRegistry.getUnknownTypeHandler();
  864.     }
  865.     return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
  866.   }

  867.   private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
  868.       String columnPrefix) throws SQLException {
  869.     final Object parameterObject = instantiateParameterObject(parameterType);
  870.     final MetaObject metaObject = configuration.newMetaObject(parameterObject);
  871.     boolean foundValues = false;
  872.     for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
  873.       final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty());
  874.       final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType);
  875.       final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix));
  876.       // issue #353 & #560 do not execute nested query if key is null
  877.       if (propValue != null) {
  878.         metaObject.setValue(innerResultMapping.getProperty(), propValue);
  879.         foundValues = true;
  880.       }
  881.     }
  882.     return foundValues ? parameterObject : null;
  883.   }

  884.   private Object instantiateParameterObject(Class<?> parameterType) {
  885.     if (parameterType == null) {
  886.       return new HashMap<>();
  887.     }
  888.     if (ParamMap.class.equals(parameterType)) {
  889.       return new HashMap<>(); // issue #649
  890.     } else {
  891.       return objectFactory.create(parameterType);
  892.     }
  893.   }

  894.   //
  895.   // DISCRIMINATOR
  896.   //

  897.   public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)
  898.       throws SQLException {
  899.     Set<String> pastDiscriminators = new HashSet<>();
  900.     Discriminator discriminator = resultMap.getDiscriminator();
  901.     while (discriminator != null) {
  902.       final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
  903.       final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
  904.       if (!configuration.hasResultMap(discriminatedMapId)) {
  905.         break;
  906.       }
  907.       resultMap = configuration.getResultMap(discriminatedMapId);
  908.       Discriminator lastDiscriminator = discriminator;
  909.       discriminator = resultMap.getDiscriminator();
  910.       if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
  911.         break;
  912.       }
  913.     }
  914.     return resultMap;
  915.   }

  916.   private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix)
  917.       throws SQLException {
  918.     final ResultMapping resultMapping = discriminator.getResultMapping();
  919.     final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
  920.     return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
  921.   }

  922.   private String prependPrefix(String columnName, String prefix) {
  923.     if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
  924.       return columnName;
  925.     }
  926.     return prefix + columnName;
  927.   }

  928.   //
  929.   // HANDLE NESTED RESULT MAPS
  930.   //

  931.   private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap,
  932.       ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  933.     final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
  934.     ResultSet resultSet = rsw.getResultSet();
  935.     skipRows(resultSet, rowBounds);
  936.     Object rowValue = previousRowValue;
  937.     while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
  938.       final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
  939.       final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
  940.       Object partialObject = nestedResultObjects.get(rowKey);
  941.       // issue #577 && #542
  942.       if (mappedStatement.isResultOrdered()) {
  943.         if (partialObject == null && rowValue != null) {
  944.           nestedResultObjects.clear();
  945.           storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  946.         }
  947.         rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
  948.       } else {
  949.         rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
  950.         if (partialObject == null) {
  951.           storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  952.         }
  953.       }
  954.     }
  955.     if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
  956.       storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  957.       previousRowValue = null;
  958.     } else if (rowValue != null) {
  959.       previousRowValue = rowValue;
  960.     }
  961.   }

  962.   //
  963.   // NESTED RESULT MAP (JOIN MAPPING)
  964.   //

  965.   private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
  966.       String parentPrefix, CacheKey parentRowKey, boolean newObject) {
  967.     boolean foundValues = false;
  968.     for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
  969.       final String nestedResultMapId = resultMapping.getNestedResultMapId();
  970.       if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
  971.         try {
  972.           final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
  973.           final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
  974.           if (resultMapping.getColumnPrefix() == null) {
  975.             // try to fill circular reference only when columnPrefix
  976.             // is not specified for the nested result map (issue #215)
  977.             Object ancestorObject = ancestorObjects.get(nestedResultMapId);
  978.             if (ancestorObject != null) {
  979.               if (newObject) {
  980.                 linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
  981.               }
  982.               continue;
  983.             }
  984.           }
  985.           final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
  986.           final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
  987.           Object rowValue = nestedResultObjects.get(combinedKey);
  988.           boolean knownValue = rowValue != null;
  989.           instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
  990.           if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
  991.             rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
  992.             if (rowValue != null && !knownValue) {
  993.               linkObjects(metaObject, resultMapping, rowValue);
  994.               foundValues = true;
  995.             }
  996.           }
  997.         } catch (SQLException e) {
  998.           throw new ExecutorException(
  999.               "Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
  1000.         }
  1001.       }
  1002.     }
  1003.     return foundValues;
  1004.   }

  1005.   private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) {
  1006.     final StringBuilder columnPrefixBuilder = new StringBuilder();
  1007.     if (parentPrefix != null) {
  1008.       columnPrefixBuilder.append(parentPrefix);
  1009.     }
  1010.     if (resultMapping.getColumnPrefix() != null) {
  1011.       columnPrefixBuilder.append(resultMapping.getColumnPrefix());
  1012.     }
  1013.     return columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH);
  1014.   }

  1015.   private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSetWrapper rsw)
  1016.       throws SQLException {
  1017.     Set<String> notNullColumns = resultMapping.getNotNullColumns();
  1018.     if (notNullColumns != null && !notNullColumns.isEmpty()) {
  1019.       ResultSet rs = rsw.getResultSet();
  1020.       for (String column : notNullColumns) {
  1021.         rs.getObject(prependPrefix(column, columnPrefix));
  1022.         if (!rs.wasNull()) {
  1023.           return true;
  1024.         }
  1025.       }
  1026.       return false;
  1027.     }
  1028.     if (columnPrefix != null) {
  1029.       for (String columnName : rsw.getColumnNames()) {
  1030.         if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix.toUpperCase(Locale.ENGLISH))) {
  1031.           return true;
  1032.         }
  1033.       }
  1034.       return false;
  1035.     }
  1036.     return true;
  1037.   }

  1038.   private ResultMap getNestedResultMap(ResultSet rs, String nestedResultMapId, String columnPrefix)
  1039.       throws SQLException {
  1040.     ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId);
  1041.     return resolveDiscriminatedResultMap(rs, nestedResultMap, columnPrefix);
  1042.   }

  1043.   //
  1044.   // UNIQUE RESULT KEY
  1045.   //

  1046.   private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
  1047.     final CacheKey cacheKey = new CacheKey();
  1048.     cacheKey.update(resultMap.getId());
  1049.     List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
  1050.     if (resultMappings.isEmpty()) {
  1051.       if (Map.class.isAssignableFrom(resultMap.getType())) {
  1052.         createRowKeyForMap(rsw, cacheKey);
  1053.       } else {
  1054.         createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
  1055.       }
  1056.     } else {
  1057.       createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
  1058.     }
  1059.     if (cacheKey.getUpdateCount() < 2) {
  1060.       return CacheKey.NULL_CACHE_KEY;
  1061.     }
  1062.     return cacheKey;
  1063.   }

  1064.   private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) {
  1065.     if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) {
  1066.       CacheKey combinedKey;
  1067.       try {
  1068.         combinedKey = rowKey.clone();
  1069.       } catch (CloneNotSupportedException e) {
  1070.         throw new ExecutorException("Error cloning cache key.  Cause: " + e, e);
  1071.       }
  1072.       combinedKey.update(parentRowKey);
  1073.       return combinedKey;
  1074.     }
  1075.     return CacheKey.NULL_CACHE_KEY;
  1076.   }

  1077.   private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {
  1078.     List<ResultMapping> resultMappings = resultMap.getIdResultMappings();
  1079.     if (resultMappings.isEmpty()) {
  1080.       resultMappings = resultMap.getPropertyResultMappings();
  1081.     }
  1082.     return resultMappings;
  1083.   }

  1084.   private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
  1085.       List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {
  1086.     for (ResultMapping resultMapping : resultMappings) {
  1087.       if (resultMapping.isSimple()) {
  1088.         final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
  1089.         final TypeHandler<?> th = resultMapping.getTypeHandler();
  1090.         Set<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
  1091.         // Issue #114
  1092.         if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
  1093.           final Object value = th.getResult(rsw.getResultSet(), column);
  1094.           if (value != null || configuration.isReturnInstanceForEmptyRow()) {
  1095.             cacheKey.update(column);
  1096.             cacheKey.update(value);
  1097.           }
  1098.         }
  1099.       }
  1100.     }
  1101.   }

  1102.   private void createRowKeyForUnmappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
  1103.       String columnPrefix) throws SQLException {
  1104.     final MetaClass metaType = MetaClass.forClass(resultMap.getType(), reflectorFactory);
  1105.     List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
  1106.     for (String column : unmappedColumnNames) {
  1107.       String property = column;
  1108.       if (columnPrefix != null && !columnPrefix.isEmpty()) {
  1109.         // When columnPrefix is specified, ignore columns without the prefix.
  1110.         if (!column.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
  1111.           continue;
  1112.         }
  1113.         property = column.substring(columnPrefix.length());
  1114.       }
  1115.       if (metaType.findProperty(property, configuration.isMapUnderscoreToCamelCase()) != null) {
  1116.         String value = rsw.getResultSet().getString(column);
  1117.         if (value != null) {
  1118.           cacheKey.update(column);
  1119.           cacheKey.update(value);
  1120.         }
  1121.       }
  1122.     }
  1123.   }

  1124.   private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException {
  1125.     List<String> columnNames = rsw.getColumnNames();
  1126.     for (String columnName : columnNames) {
  1127.       final String value = rsw.getResultSet().getString(columnName);
  1128.       if (value != null) {
  1129.         cacheKey.update(columnName);
  1130.         cacheKey.update(value);
  1131.       }
  1132.     }
  1133.   }

  1134.   private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
  1135.     final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
  1136.     if (collectionProperty != null) {
  1137.       final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
  1138.       targetMetaObject.add(rowValue);
  1139.     } else {
  1140.       metaObject.setValue(resultMapping.getProperty(), rowValue);
  1141.     }
  1142.   }

  1143.   private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
  1144.     final String propertyName = resultMapping.getProperty();
  1145.     Object propertyValue = metaObject.getValue(propertyName);
  1146.     if (propertyValue == null) {
  1147.       Class<?> type = resultMapping.getJavaType();
  1148.       if (type == null) {
  1149.         type = metaObject.getSetterType(propertyName);
  1150.       }
  1151.       try {
  1152.         if (objectFactory.isCollection(type)) {
  1153.           propertyValue = objectFactory.create(type);
  1154.           metaObject.setValue(propertyName, propertyValue);
  1155.           return propertyValue;
  1156.         }
  1157.       } catch (Exception e) {
  1158.         throw new ExecutorException(
  1159.             "Error instantiating collection property for result '" + resultMapping.getProperty() + "'.  Cause: " + e,
  1160.             e);
  1161.       }
  1162.     } else if (objectFactory.isCollection(propertyValue.getClass())) {
  1163.       return propertyValue;
  1164.     }
  1165.     return null;
  1166.   }

  1167.   private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
  1168.     if (rsw.getColumnNames().size() == 1) {
  1169.       return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0)));
  1170.     }
  1171.     return typeHandlerRegistry.hasTypeHandler(resultType);
  1172.   }

  1173. }