DefaultResultSetHandler.java

  1. /*
  2.  *    Copyright 2009-2022 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.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, ResultHandler<?> resultHandler, BoundSql boundSql,
  115.                                  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) throws SQLException {
  148.     if (rs == null) {
  149.       return;
  150.     }
  151.     try {
  152.       final String resultMapId = parameterMapping.getResultMapId();
  153.       final ResultMap resultMap = configuration.getResultMap(resultMapId);
  154.       final ResultSetWrapper rsw = new ResultSetWrapper(rs, configuration);
  155.       if (this.resultHandler == null) {
  156.         final DefaultResultHandler resultHandler = new DefaultResultHandler(objectFactory);
  157.         handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
  158.         metaParam.setValue(parameterMapping.getProperty(), resultHandler.getResultList());
  159.       } else {
  160.         handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
  161.       }
  162.     } finally {
  163.       // issue #228 (close resultsets)
  164.       closeResultSet(rs);
  165.     }
  166.   }

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

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

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

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

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

  200.     return collapseSingleResultList(multipleResults);
  201.   }

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

  205.     ResultSetWrapper rsw = getFirstResultSet(stmt);

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

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

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

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

  231.   private ResultSetWrapper getNextResultSet(Statement stmt) {
  232.     // Making this method tolerant of bad JDBC drivers
  233.     try {
  234.       if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
  235.         // Crazy Standard JDBC way of determining if there are more results
  236.         if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
  237.           ResultSet rs = stmt.getResultSet();
  238.           if (rs == null) {
  239.             return getNextResultSet(stmt);
  240.           } else {
  241.             return new ResultSetWrapper(rs, configuration);
  242.           }
  243.         }
  244.       }
  245.     } catch (Exception e) {
  246.       // Intentionally ignored.
  247.     }
  248.     return null;
  249.   }

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

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

  262.   private void validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount) {
  263.     if (rsw != null && resultMapCount < 1) {
  264.       throw new ExecutorException("A query was run and no Result Maps were found for the Mapped Statement '" + mappedStatement.getId()
  265.           + "'.  It's likely that neither a Result Type nor a Result Map was specified.");
  266.     }
  267.   }

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

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

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

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

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

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

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

  326.   private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
  327.     if (parentMapping != null) {
  328.       linkToParents(rs, parentMapping, rowValue);
  329.     } else {
  330.       callResultHandler(resultHandler, resultContext, rowValue);
  331.     }
  332.   }

  333.   @SuppressWarnings("unchecked" /* because ResultHandler<?> is always ResultHandler<Object>*/)
  334.   private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
  335.     resultContext.nextResultObject(rowValue);
  336.     ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
  337.   }

  338.   private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
  339.     return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
  340.   }

  341.   private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
  342.     if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
  343.       if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
  344.         rs.absolute(rowBounds.getOffset());
  345.       }
  346.     } else {
  347.       for (int i = 0; i < rowBounds.getOffset(); i++) {
  348.         if (!rs.next()) {
  349.           break;
  350.         }
  351.       }
  352.     }
  353.   }

  354.   //
  355.   // GET VALUE FROM ROW FOR SIMPLE RESULT MAP
  356.   //

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

  372.   //
  373.   // GET VALUE FROM ROW FOR NESTED RESULT MAP
  374.   //

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

  405.   private void putAncestor(Object resultObject, String resultMapId) {
  406.     ancestorObjects.put(resultMapId, resultObject);
  407.   }

  408.   private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
  409.     if (resultMap.getAutoMapping() != null) {
  410.       return resultMap.getAutoMapping();
  411.     } else {
  412.       if (isNested) {
  413.         return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
  414.       } else {
  415.         return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
  416.       }
  417.     }
  418.   }

  419.   //
  420.   // PROPERTY MAPPINGS
  421.   //

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

  456.   private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
  457.       throws SQLException {
  458.     if (propertyMapping.getNestedQueryId() != null) {
  459.       return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
  460.     } else if (propertyMapping.getResultSet() != null) {
  461.       addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
  462.       return DEFERRED;
  463.     } else {
  464.       final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
  465.       final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
  466.       return typeHandler.getResult(rs, column);
  467.     }
  468.   }

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

  513.   private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
  514.     List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
  515.     boolean foundValues = false;
  516.     if (!autoMapping.isEmpty()) {
  517.       for (UnMappedColumnAutoMapping mapping : autoMapping) {
  518.         final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
  519.         if (value != null) {
  520.           foundValues = true;
  521.         }
  522.         if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
  523.           // gcode issue #377, call setter on nulls (value is not 'found')
  524.           metaObject.setValue(mapping.property, value);
  525.         }
  526.       }
  527.     }
  528.     return foundValues;
  529.   }

  530.   // MULTIPLE RESULT SETS

  531.   private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException {
  532.     CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), parentMapping.getForeignColumn());
  533.     List<PendingRelation> parents = pendingRelations.get(parentKey);
  534.     if (parents != null) {
  535.       for (PendingRelation parent : parents) {
  536.         if (parent != null && rowValue != null) {
  537.           linkObjects(parent.metaObject, parent.propertyMapping, rowValue);
  538.         }
  539.       }
  540.     }
  541.   }

  542.   private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, ResultMapping parentMapping) throws SQLException {
  543.     CacheKey cacheKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), parentMapping.getColumn());
  544.     PendingRelation deferLoad = new PendingRelation();
  545.     deferLoad.metaObject = metaResultObject;
  546.     deferLoad.propertyMapping = parentMapping;
  547.     List<PendingRelation> relations = MapUtil.computeIfAbsent(pendingRelations, cacheKey, k -> new ArrayList<>());
  548.     // issue #255
  549.     relations.add(deferLoad);
  550.     ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet());
  551.     if (previous == null) {
  552.       nextResultMaps.put(parentMapping.getResultSet(), parentMapping);
  553.     } else {
  554.       if (!previous.equals(parentMapping)) {
  555.         throw new ExecutorException("Two different properties are mapped to the same resultSet");
  556.       }
  557.     }
  558.   }

  559.   private CacheKey createKeyForMultipleResults(ResultSet rs, ResultMapping resultMapping, String names, String columns) throws SQLException {
  560.     CacheKey cacheKey = new CacheKey();
  561.     cacheKey.update(resultMapping);
  562.     if (columns != null && names != null) {
  563.       String[] columnsArray = columns.split(",");
  564.       String[] namesArray = names.split(",");
  565.       for (int i = 0; i < columnsArray.length; i++) {
  566.         Object value = rs.getString(columnsArray[i]);
  567.         if (value != null) {
  568.           cacheKey.update(namesArray[i]);
  569.           cacheKey.update(value);
  570.         }
  571.       }
  572.     }
  573.     return cacheKey;
  574.   }

  575.   //
  576.   // INSTANTIATION & CONSTRUCTOR MAPPING
  577.   //

  578.   private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
  579.     this.useConstructorMappings = false; // reset previous mapping result
  580.     final List<Class<?>> constructorArgTypes = new ArrayList<>();
  581.     final List<Object> constructorArgs = new ArrayList<>();
  582.     Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
  583.     if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
  584.       final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  585.       for (ResultMapping propertyMapping : propertyMappings) {
  586.         // issue gcode #109 && issue #149
  587.         if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
  588.           resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
  589.           break;
  590.         }
  591.       }
  592.     }
  593.     this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
  594.     return resultObject;
  595.   }

  596.   private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
  597.       throws SQLException {
  598.     final Class<?> resultType = resultMap.getType();
  599.     final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
  600.     final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
  601.     if (hasTypeHandlerForResultObject(rsw, resultType)) {
  602.       return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
  603.     } else if (!constructorMappings.isEmpty()) {
  604.       return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
  605.     } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
  606.       return objectFactory.create(resultType);
  607.     } else if (shouldApplyAutomaticMappings(resultMap, false)) {
  608.       return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs);
  609.     }
  610.     throw new ExecutorException("Do not know how to create an instance of " + resultType);
  611.   }

  612.   Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings,
  613.                                          List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) {
  614.     boolean foundValues = false;
  615.     for (ResultMapping constructorMapping : constructorMappings) {
  616.       final Class<?> parameterType = constructorMapping.getJavaType();
  617.       final String column = constructorMapping.getColumn();
  618.       final Object value;
  619.       try {
  620.         if (constructorMapping.getNestedQueryId() != null) {
  621.           value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
  622.         } else if (constructorMapping.getNestedResultMapId() != null) {
  623.           final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
  624.           value = getRowValue(rsw, resultMap, getColumnPrefix(columnPrefix, constructorMapping));
  625.         } else {
  626.           final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
  627.           value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
  628.         }
  629.       } catch (ResultMapException | SQLException e) {
  630.         throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
  631.       }
  632.       constructorArgTypes.add(parameterType);
  633.       constructorArgs.add(value);
  634.       foundValues = value != null || foundValues;
  635.     }
  636.     return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
  637.   }

  638.   private Object createByConstructorSignature(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, Class<?> resultType,
  639.       List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
  640.     return applyConstructorAutomapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs,
  641.         findConstructorForAutomapping(resultType, rsw).orElseThrow(() -> new ExecutorException(
  642.             "No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames())));
  643.   }

  644.   private Optional<Constructor<?>> findConstructorForAutomapping(final Class<?> resultType, ResultSetWrapper rsw) {
  645.     Constructor<?>[] constructors = resultType.getDeclaredConstructors();
  646.     if (constructors.length == 1) {
  647.       return Optional.of(constructors[0]);
  648.     }
  649.     for (final Constructor<?> constructor : constructors) {
  650.       if (constructor.isAnnotationPresent(AutomapConstructor.class)) {
  651.         return Optional.of(constructor);
  652.       }
  653.     }
  654.     if (configuration.isArgNameBasedConstructorAutoMapping()) {
  655.       // Finding-best-match type implementation is possible,
  656.       // but using @AutomapConstructor seems sufficient.
  657.       throw new ExecutorException(MessageFormat.format(
  658.           "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.",
  659.           resultType.getName()));
  660.     } else {
  661.       return Arrays.stream(constructors).filter(x -> findUsableConstructorByArgTypes(x, rsw.getJdbcTypes())).findAny();
  662.     }
  663.   }

  664.   private boolean findUsableConstructorByArgTypes(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) {
  665.     final Class<?>[] parameterTypes = constructor.getParameterTypes();
  666.     if (parameterTypes.length != jdbcTypes.size()) {
  667.       return false;
  668.     }
  669.     for (int i = 0; i < parameterTypes.length; i++) {
  670.       if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) {
  671.         return false;
  672.       }
  673.     }
  674.     return true;
  675.   }

  676.   private Object applyConstructorAutomapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor) throws SQLException {
  677.     boolean foundValues = false;
  678.     if (configuration.isArgNameBasedConstructorAutoMapping()) {
  679.       foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs,
  680.           constructor, foundValues);
  681.     } else {
  682.       foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor,
  683.           foundValues);
  684.     }
  685.     return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
  686.   }

  687.   private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,
  688.       List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {
  689.     for (int i = 0; i < constructor.getParameterTypes().length; i++) {
  690.       Class<?> parameterType = constructor.getParameterTypes()[i];
  691.       String columnName = rsw.getColumnNames().get(i);
  692.       TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
  693.       Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
  694.       constructorArgTypes.add(parameterType);
  695.       constructorArgs.add(value);
  696.       foundValues = value != null || foundValues;
  697.     }
  698.     return foundValues;
  699.   }

  700.   private boolean applyArgNameBasedConstructorAutoMapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, Class<?> resultType,
  701.       List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues)
  702.       throws SQLException {
  703.     List<String> missingArgs = null;
  704.     Parameter[] params = constructor.getParameters();
  705.     for (Parameter param : params) {
  706.       boolean columnNotFound = true;
  707.       Param paramAnno = param.getAnnotation(Param.class);
  708.       String paramName = paramAnno == null ? param.getName() : paramAnno.value();
  709.       for (String columnName : rsw.getColumnNames()) {
  710.         if (columnMatchesParam(columnName, paramName, columnPrefix)) {
  711.           Class<?> paramType = param.getType();
  712.           TypeHandler<?> typeHandler = rsw.getTypeHandler(paramType, columnName);
  713.           Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
  714.           constructorArgTypes.add(paramType);
  715.           constructorArgs.add(value);
  716.           final String mapKey = resultMap.getId() + ":" + columnPrefix;
  717.           if (!autoMappingsCache.containsKey(mapKey)) {
  718.             MapUtil.computeIfAbsent(constructorAutoMappingColumns, mapKey, k -> new ArrayList<>()).add(columnName);
  719.           }
  720.           columnNotFound = false;
  721.           foundValues = value != null || foundValues;
  722.         }
  723.       }
  724.       if (columnNotFound) {
  725.         if (missingArgs == null) {
  726.           missingArgs = new ArrayList<>();
  727.         }
  728.         missingArgs.add(paramName);
  729.       }
  730.     }
  731.     if (foundValues && constructorArgs.size() < params.length) {
  732.       throw new ExecutorException(MessageFormat.format("Constructor auto-mapping of ''{1}'' failed "
  733.           + "because ''{0}'' were not found in the result set; "
  734.           + "Available columns are ''{2}'' and mapUnderscoreToCamelCase is ''{3}''.",
  735.           missingArgs, constructor, rsw.getColumnNames(), configuration.isMapUnderscoreToCamelCase()));
  736.     }
  737.     return foundValues;
  738.   }

  739.   private boolean columnMatchesParam(String columnName, String paramName, String columnPrefix) {
  740.     if (columnPrefix != null) {
  741.       if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
  742.         return false;
  743.       }
  744.       columnName = columnName.substring(columnPrefix.length());
  745.     }
  746.     return paramName
  747.         .equalsIgnoreCase(configuration.isMapUnderscoreToCamelCase() ? columnName.replace("_", "") : columnName);
  748.   }

  749.   private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
  750.     final Class<?> resultType = resultMap.getType();
  751.     final String columnName;
  752.     if (!resultMap.getResultMappings().isEmpty()) {
  753.       final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
  754.       final ResultMapping mapping = resultMappingList.get(0);
  755.       columnName = prependPrefix(mapping.getColumn(), columnPrefix);
  756.     } else {
  757.       columnName = rsw.getColumnNames().get(0);
  758.     }
  759.     final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
  760.     return typeHandler.getResult(rsw.getResultSet(), columnName);
  761.   }

  762.   //
  763.   // NESTED QUERY
  764.   //

  765.   private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix) throws SQLException {
  766.     final String nestedQueryId = constructorMapping.getNestedQueryId();
  767.     final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
  768.     final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
  769.     final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping, nestedQueryParameterType, columnPrefix);
  770.     Object value = null;
  771.     if (nestedQueryParameterObject != null) {
  772.       final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
  773.       final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
  774.       final Class<?> targetType = constructorMapping.getJavaType();
  775.       final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
  776.       value = resultLoader.loadResult();
  777.     }
  778.     return value;
  779.   }

  780.   private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
  781.       throws SQLException {
  782.     final String nestedQueryId = propertyMapping.getNestedQueryId();
  783.     final String property = propertyMapping.getProperty();
  784.     final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
  785.     final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
  786.     final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
  787.     Object value = null;
  788.     if (nestedQueryParameterObject != null) {
  789.       final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
  790.       final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
  791.       final Class<?> targetType = propertyMapping.getJavaType();
  792.       if (executor.isCached(nestedQuery, key)) {
  793.         executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
  794.         value = DEFERRED;
  795.       } else {
  796.         final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
  797.         if (propertyMapping.isLazy()) {
  798.           lazyLoader.addLoader(property, metaResultObject, resultLoader);
  799.           value = DEFERRED;
  800.         } else {
  801.           value = resultLoader.loadResult();
  802.         }
  803.       }
  804.     }
  805.     return value;
  806.   }

  807.   private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
  808.     if (resultMapping.isCompositeResult()) {
  809.       return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix);
  810.     } else {
  811.       return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix);
  812.     }
  813.   }

  814.   private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
  815.     final TypeHandler<?> typeHandler;
  816.     if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
  817.       typeHandler = typeHandlerRegistry.getTypeHandler(parameterType);
  818.     } else {
  819.       typeHandler = typeHandlerRegistry.getUnknownTypeHandler();
  820.     }
  821.     return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
  822.   }

  823.   private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
  824.     final Object parameterObject = instantiateParameterObject(parameterType);
  825.     final MetaObject metaObject = configuration.newMetaObject(parameterObject);
  826.     boolean foundValues = false;
  827.     for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
  828.       final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty());
  829.       final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType);
  830.       final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix));
  831.       // issue #353 & #560 do not execute nested query if key is null
  832.       if (propValue != null) {
  833.         metaObject.setValue(innerResultMapping.getProperty(), propValue);
  834.         foundValues = true;
  835.       }
  836.     }
  837.     return foundValues ? parameterObject : null;
  838.   }

  839.   private Object instantiateParameterObject(Class<?> parameterType) {
  840.     if (parameterType == null) {
  841.       return new HashMap<>();
  842.     } else if (ParamMap.class.equals(parameterType)) {
  843.       return new HashMap<>(); // issue #649
  844.     } else {
  845.       return objectFactory.create(parameterType);
  846.     }
  847.   }

  848.   //
  849.   // DISCRIMINATOR
  850.   //

  851.   public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException {
  852.     Set<String> pastDiscriminators = new HashSet<>();
  853.     Discriminator discriminator = resultMap.getDiscriminator();
  854.     while (discriminator != null) {
  855.       final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
  856.       final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
  857.       if (configuration.hasResultMap(discriminatedMapId)) {
  858.         resultMap = configuration.getResultMap(discriminatedMapId);
  859.         Discriminator lastDiscriminator = discriminator;
  860.         discriminator = resultMap.getDiscriminator();
  861.         if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
  862.           break;
  863.         }
  864.       } else {
  865.         break;
  866.       }
  867.     }
  868.     return resultMap;
  869.   }

  870.   private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix) throws SQLException {
  871.     final ResultMapping resultMapping = discriminator.getResultMapping();
  872.     final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
  873.     return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
  874.   }

  875.   private String prependPrefix(String columnName, String prefix) {
  876.     if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
  877.       return columnName;
  878.     }
  879.     return prefix + columnName;
  880.   }

  881.   //
  882.   // HANDLE NESTED RESULT MAPS
  883.   //

  884.   private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  885.     final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
  886.     ResultSet resultSet = rsw.getResultSet();
  887.     skipRows(resultSet, rowBounds);
  888.     Object rowValue = previousRowValue;
  889.     while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
  890.       final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
  891.       final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
  892.       Object partialObject = nestedResultObjects.get(rowKey);
  893.       // issue #577 && #542
  894.       if (mappedStatement.isResultOrdered()) {
  895.         if (partialObject == null && rowValue != null) {
  896.           nestedResultObjects.clear();
  897.           storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  898.         }
  899.         rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
  900.       } else {
  901.         rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
  902.         if (partialObject == null) {
  903.           storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  904.         }
  905.       }
  906.     }
  907.     if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
  908.       storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  909.       previousRowValue = null;
  910.     } else if (rowValue != null) {
  911.       previousRowValue = rowValue;
  912.     }
  913.   }

  914.   //
  915.   // NESTED RESULT MAP (JOIN MAPPING)
  916.   //

  917.   private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {
  918.     boolean foundValues = false;
  919.     for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
  920.       final String nestedResultMapId = resultMapping.getNestedResultMapId();
  921.       if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
  922.         try {
  923.           final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
  924.           final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
  925.           if (resultMapping.getColumnPrefix() == null) {
  926.             // try to fill circular reference only when columnPrefix
  927.             // is not specified for the nested result map (issue #215)
  928.             Object ancestorObject = ancestorObjects.get(nestedResultMapId);
  929.             if (ancestorObject != null) {
  930.               if (newObject) {
  931.                 linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
  932.               }
  933.               continue;
  934.             }
  935.           }
  936.           final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
  937.           final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
  938.           Object rowValue = nestedResultObjects.get(combinedKey);
  939.           boolean knownValue = rowValue != null;
  940.           instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
  941.           if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
  942.             rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
  943.             if (rowValue != null && !knownValue) {
  944.               linkObjects(metaObject, resultMapping, rowValue);
  945.               foundValues = true;
  946.             }
  947.           }
  948.         } catch (SQLException e) {
  949.           throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
  950.         }
  951.       }
  952.     }
  953.     return foundValues;
  954.   }

  955.   private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) {
  956.     final StringBuilder columnPrefixBuilder = new StringBuilder();
  957.     if (parentPrefix != null) {
  958.       columnPrefixBuilder.append(parentPrefix);
  959.     }
  960.     if (resultMapping.getColumnPrefix() != null) {
  961.       columnPrefixBuilder.append(resultMapping.getColumnPrefix());
  962.     }
  963.     return columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH);
  964.   }

  965.   private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSetWrapper rsw) throws SQLException {
  966.     Set<String> notNullColumns = resultMapping.getNotNullColumns();
  967.     if (notNullColumns != null && !notNullColumns.isEmpty()) {
  968.       ResultSet rs = rsw.getResultSet();
  969.       for (String column : notNullColumns) {
  970.         rs.getObject(prependPrefix(column, columnPrefix));
  971.         if (!rs.wasNull()) {
  972.           return true;
  973.         }
  974.       }
  975.       return false;
  976.     } else if (columnPrefix != null) {
  977.       for (String columnName : rsw.getColumnNames()) {
  978.         if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix.toUpperCase(Locale.ENGLISH))) {
  979.           return true;
  980.         }
  981.       }
  982.       return false;
  983.     }
  984.     return true;
  985.   }

  986.   private ResultMap getNestedResultMap(ResultSet rs, String nestedResultMapId, String columnPrefix) throws SQLException {
  987.     ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId);
  988.     return resolveDiscriminatedResultMap(rs, nestedResultMap, columnPrefix);
  989.   }

  990.   //
  991.   // UNIQUE RESULT KEY
  992.   //

  993.   private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
  994.     final CacheKey cacheKey = new CacheKey();
  995.     cacheKey.update(resultMap.getId());
  996.     List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
  997.     if (resultMappings.isEmpty()) {
  998.       if (Map.class.isAssignableFrom(resultMap.getType())) {
  999.         createRowKeyForMap(rsw, cacheKey);
  1000.       } else {
  1001.         createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
  1002.       }
  1003.     } else {
  1004.       createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
  1005.     }
  1006.     if (cacheKey.getUpdateCount() < 2) {
  1007.       return CacheKey.NULL_CACHE_KEY;
  1008.     }
  1009.     return cacheKey;
  1010.   }

  1011.   private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) {
  1012.     if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) {
  1013.       CacheKey combinedKey;
  1014.       try {
  1015.         combinedKey = rowKey.clone();
  1016.       } catch (CloneNotSupportedException e) {
  1017.         throw new ExecutorException("Error cloning cache key.  Cause: " + e, e);
  1018.       }
  1019.       combinedKey.update(parentRowKey);
  1020.       return combinedKey;
  1021.     }
  1022.     return CacheKey.NULL_CACHE_KEY;
  1023.   }

  1024.   private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {
  1025.     List<ResultMapping> resultMappings = resultMap.getIdResultMappings();
  1026.     if (resultMappings.isEmpty()) {
  1027.       resultMappings = resultMap.getPropertyResultMappings();
  1028.     }
  1029.     return resultMappings;
  1030.   }

  1031.   private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {
  1032.     for (ResultMapping resultMapping : resultMappings) {
  1033.       if (resultMapping.isSimple()) {
  1034.         final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
  1035.         final TypeHandler<?> th = resultMapping.getTypeHandler();
  1036.         List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
  1037.         // Issue #114
  1038.         if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
  1039.           final Object value = th.getResult(rsw.getResultSet(), column);
  1040.           if (value != null || configuration.isReturnInstanceForEmptyRow()) {
  1041.             cacheKey.update(column);
  1042.             cacheKey.update(value);
  1043.           }
  1044.         }
  1045.       }
  1046.     }
  1047.   }

  1048.   private void createRowKeyForUnmappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, String columnPrefix) throws SQLException {
  1049.     final MetaClass metaType = MetaClass.forClass(resultMap.getType(), reflectorFactory);
  1050.     List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
  1051.     for (String column : unmappedColumnNames) {
  1052.       String property = column;
  1053.       if (columnPrefix != null && !columnPrefix.isEmpty()) {
  1054.         // When columnPrefix is specified, ignore columns without the prefix.
  1055.         if (column.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
  1056.           property = column.substring(columnPrefix.length());
  1057.         } else {
  1058.           continue;
  1059.         }
  1060.       }
  1061.       if (metaType.findProperty(property, configuration.isMapUnderscoreToCamelCase()) != null) {
  1062.         String value = rsw.getResultSet().getString(column);
  1063.         if (value != null) {
  1064.           cacheKey.update(column);
  1065.           cacheKey.update(value);
  1066.         }
  1067.       }
  1068.     }
  1069.   }

  1070.   private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException {
  1071.     List<String> columnNames = rsw.getColumnNames();
  1072.     for (String columnName : columnNames) {
  1073.       final String value = rsw.getResultSet().getString(columnName);
  1074.       if (value != null) {
  1075.         cacheKey.update(columnName);
  1076.         cacheKey.update(value);
  1077.       }
  1078.     }
  1079.   }

  1080.   private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
  1081.     final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
  1082.     if (collectionProperty != null) {
  1083.       final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
  1084.       targetMetaObject.add(rowValue);
  1085.     } else {
  1086.       metaObject.setValue(resultMapping.getProperty(), rowValue);
  1087.     }
  1088.   }

  1089.   private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
  1090.     final String propertyName = resultMapping.getProperty();
  1091.     Object propertyValue = metaObject.getValue(propertyName);
  1092.     if (propertyValue == null) {
  1093.       Class<?> type = resultMapping.getJavaType();
  1094.       if (type == null) {
  1095.         type = metaObject.getSetterType(propertyName);
  1096.       }
  1097.       try {
  1098.         if (objectFactory.isCollection(type)) {
  1099.           propertyValue = objectFactory.create(type);
  1100.           metaObject.setValue(propertyName, propertyValue);
  1101.           return propertyValue;
  1102.         }
  1103.       } catch (Exception e) {
  1104.         throw new ExecutorException("Error instantiating collection property for result '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
  1105.       }
  1106.     } else if (objectFactory.isCollection(propertyValue.getClass())) {
  1107.       return propertyValue;
  1108.     }
  1109.     return null;
  1110.   }

  1111.   private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
  1112.     if (rsw.getColumnNames().size() == 1) {
  1113.       return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0)));
  1114.     }
  1115.     return typeHandlerRegistry.hasTypeHandler(resultType);
  1116.   }

  1117. }