View Javadoc
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  
18  import java.lang.reflect.Constructor;
19  import java.lang.reflect.Parameter;
20  import java.sql.CallableStatement;
21  import java.sql.ResultSet;
22  import java.sql.SQLException;
23  import java.sql.Statement;
24  import java.text.MessageFormat;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Locale;
31  import java.util.Map;
32  import java.util.Optional;
33  import java.util.Set;
34  
35  import org.apache.ibatis.annotations.AutomapConstructor;
36  import org.apache.ibatis.annotations.Param;
37  import org.apache.ibatis.binding.MapperMethod.ParamMap;
38  import org.apache.ibatis.cache.CacheKey;
39  import org.apache.ibatis.cursor.Cursor;
40  import org.apache.ibatis.cursor.defaults.DefaultCursor;
41  import org.apache.ibatis.executor.ErrorContext;
42  import org.apache.ibatis.executor.Executor;
43  import org.apache.ibatis.executor.ExecutorException;
44  import org.apache.ibatis.executor.loader.ResultLoader;
45  import org.apache.ibatis.executor.loader.ResultLoaderMap;
46  import org.apache.ibatis.executor.parameter.ParameterHandler;
47  import org.apache.ibatis.executor.result.DefaultResultContext;
48  import org.apache.ibatis.executor.result.DefaultResultHandler;
49  import org.apache.ibatis.executor.result.ResultMapException;
50  import org.apache.ibatis.mapping.BoundSql;
51  import org.apache.ibatis.mapping.Discriminator;
52  import org.apache.ibatis.mapping.MappedStatement;
53  import org.apache.ibatis.mapping.ParameterMapping;
54  import org.apache.ibatis.mapping.ParameterMode;
55  import org.apache.ibatis.mapping.ResultMap;
56  import org.apache.ibatis.mapping.ResultMapping;
57  import org.apache.ibatis.reflection.MetaClass;
58  import org.apache.ibatis.reflection.MetaObject;
59  import org.apache.ibatis.reflection.ReflectorFactory;
60  import org.apache.ibatis.reflection.factory.ObjectFactory;
61  import org.apache.ibatis.session.AutoMappingBehavior;
62  import org.apache.ibatis.session.Configuration;
63  import org.apache.ibatis.session.ResultContext;
64  import org.apache.ibatis.session.ResultHandler;
65  import org.apache.ibatis.session.RowBounds;
66  import org.apache.ibatis.type.JdbcType;
67  import org.apache.ibatis.type.TypeHandler;
68  import org.apache.ibatis.type.TypeHandlerRegistry;
69  import org.apache.ibatis.util.MapUtil;
70  
71  /**
72   * @author Clinton Begin
73   * @author Eduardo Macarron
74   * @author Iwao AVE!
75   * @author Kazuki Shimizu
76   */
77  public class DefaultResultSetHandler implements ResultSetHandler {
78  
79    private static final Object DEFERRED = new Object();
80  
81    private final Executor executor;
82    private final Configuration configuration;
83    private final MappedStatement mappedStatement;
84    private final RowBounds rowBounds;
85    private final ParameterHandler parameterHandler;
86    private final ResultHandler<?> resultHandler;
87    private final BoundSql boundSql;
88    private final TypeHandlerRegistry typeHandlerRegistry;
89    private final ObjectFactory objectFactory;
90    private final ReflectorFactory reflectorFactory;
91  
92    // nested resultmaps
93    private final Map<CacheKey, Object> nestedResultObjects = new HashMap<>();
94    private final Map<String, Object> ancestorObjects = new HashMap<>();
95    private Object previousRowValue;
96  
97    // multiple resultsets
98    private final Map<String, ResultMapping> nextResultMaps = new HashMap<>();
99    private final Map<CacheKey, List<PendingRelation>> pendingRelations = new HashMap<>();
100 
101   // Cached Automappings
102   private final Map<String, List<UnMappedColumnAutoMapping>> autoMappingsCache = new HashMap<>();
103   private final Map<String, List<String>> constructorAutoMappingColumns = new HashMap<>();
104 
105   // temporary marking flag that indicate using constructor mapping (use field to reduce memory usage)
106   private boolean useConstructorMappings;
107 
108   private static class PendingRelation {
109     public MetaObject metaObject;
110     public ResultMapping propertyMapping;
111   }
112 
113   private static class UnMappedColumnAutoMapping {
114     private final String column;
115     private final String property;
116     private final TypeHandler<?> typeHandler;
117     private final boolean primitive;
118 
119     public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
120       this.column = column;
121       this.property = property;
122       this.typeHandler = typeHandler;
123       this.primitive = primitive;
124     }
125   }
126 
127   public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler<?> resultHandler, BoundSql boundSql,
128                                  RowBounds rowBounds) {
129     this.executor = executor;
130     this.configuration = mappedStatement.getConfiguration();
131     this.mappedStatement = mappedStatement;
132     this.rowBounds = rowBounds;
133     this.parameterHandler = parameterHandler;
134     this.boundSql = boundSql;
135     this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
136     this.objectFactory = configuration.getObjectFactory();
137     this.reflectorFactory = configuration.getReflectorFactory();
138     this.resultHandler = resultHandler;
139   }
140 
141   //
142   // HANDLE OUTPUT PARAMETER
143   //
144 
145   @Override
146   public void handleOutputParameters(CallableStatement cs) throws SQLException {
147     final Object parameterObject = parameterHandler.getParameterObject();
148     final MetaObject metaParam = configuration.newMetaObject(parameterObject);
149     final List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
150     for (int i = 0; i < parameterMappings.size(); i++) {
151       final ParameterMapping parameterMapping = parameterMappings.get(i);
152       if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) {
153         if (ResultSet.class.equals(parameterMapping.getJavaType())) {
154           handleRefCursorOutputParameter((ResultSet) cs.getObject(i + 1), parameterMapping, metaParam);
155         } else {
156           final TypeHandler<?> typeHandler = parameterMapping.getTypeHandler();
157           metaParam.setValue(parameterMapping.getProperty(), typeHandler.getResult(cs, i + 1));
158         }
159       }
160     }
161   }
162 
163   private void handleRefCursorOutputParameter(ResultSet rs, ParameterMapping parameterMapping, MetaObject metaParam) throws SQLException {
164     if (rs == null) {
165       return;
166     }
167     try {
168       final String resultMapId = parameterMapping.getResultMapId();
169       final ResultMap resultMap = configuration.getResultMap(resultMapId);
170       final ResultSetWrapper rsw = new ResultSetWrapper(rs, configuration);
171       if (this.resultHandler == null) {
172         final DefaultResultHandler resultHandler = new DefaultResultHandler(objectFactory);
173         handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
174         metaParam.setValue(parameterMapping.getProperty(), resultHandler.getResultList());
175       } else {
176         handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
177       }
178     } finally {
179       // issue #228 (close resultsets)
180       closeResultSet(rs);
181     }
182   }
183 
184   //
185   // HANDLE RESULT SETS
186   //
187   @Override
188   public List<Object> handleResultSets(Statement stmt) throws SQLException {
189     ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
190 
191     final List<Object> multipleResults = new ArrayList<>();
192 
193     int resultSetCount = 0;
194     ResultSetWrapper rsw = getFirstResultSet(stmt);
195 
196     List<ResultMap> resultMaps = mappedStatement.getResultMaps();
197     int resultMapCount = resultMaps.size();
198     validateResultMapsCount(rsw, resultMapCount);
199     while (rsw != null && resultMapCount > resultSetCount) {
200       ResultMap resultMap = resultMaps.get(resultSetCount);
201       handleResultSet(rsw, resultMap, multipleResults, null);
202       rsw = getNextResultSet(stmt);
203       cleanUpAfterHandlingResultSet();
204       resultSetCount++;
205     }
206 
207     String[] resultSets = mappedStatement.getResultSets();
208     if (resultSets != null) {
209       while (rsw != null && resultSetCount < resultSets.length) {
210         ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
211         if (parentMapping != null) {
212           String nestedResultMapId = parentMapping.getNestedResultMapId();
213           ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
214           handleResultSet(rsw, resultMap, null, parentMapping);
215         }
216         rsw = getNextResultSet(stmt);
217         cleanUpAfterHandlingResultSet();
218         resultSetCount++;
219       }
220     }
221 
222     return collapseSingleResultList(multipleResults);
223   }
224 
225   @Override
226   public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException {
227     ErrorContext.instance().activity("handling cursor results").object(mappedStatement.getId());
228 
229     ResultSetWrapper rsw = getFirstResultSet(stmt);
230 
231     List<ResultMap> resultMaps = mappedStatement.getResultMaps();
232 
233     int resultMapCount = resultMaps.size();
234     validateResultMapsCount(rsw, resultMapCount);
235     if (resultMapCount != 1) {
236       throw new ExecutorException("Cursor results cannot be mapped to multiple resultMaps");
237     }
238 
239     ResultMap resultMap = resultMaps.get(0);
240     return new DefaultCursor<>(this, resultMap, rsw, rowBounds);
241   }
242 
243   private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
244     ResultSet rs = stmt.getResultSet();
245     while (rs == null) {
246       // move forward to get the first resultset in case the driver
247       // doesn't return the resultset as the first result (HSQLDB 2.1)
248       if (stmt.getMoreResults()) {
249         rs = stmt.getResultSet();
250       } else {
251         if (stmt.getUpdateCount() == -1) {
252           // no more results. Must be no resultset
253           break;
254         }
255       }
256     }
257     return rs != null ? new ResultSetWrapper(rs, configuration) : null;
258   }
259 
260   private ResultSetWrapper getNextResultSet(Statement stmt) {
261     // Making this method tolerant of bad JDBC drivers
262     try {
263       if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
264         // Crazy Standard JDBC way of determining if there are more results
265         if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
266           ResultSet rs = stmt.getResultSet();
267           if (rs == null) {
268             return getNextResultSet(stmt);
269           } else {
270             return new ResultSetWrapper(rs, configuration);
271           }
272         }
273       }
274     } catch (Exception e) {
275       // Intentionally ignored.
276     }
277     return null;
278   }
279 
280   private void closeResultSet(ResultSet rs) {
281     try {
282       if (rs != null) {
283         rs.close();
284       }
285     } catch (SQLException e) {
286       // ignore
287     }
288   }
289 
290   private void cleanUpAfterHandlingResultSet() {
291     nestedResultObjects.clear();
292   }
293 
294   private void validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount) {
295     if (rsw != null && resultMapCount < 1) {
296       throw new ExecutorException("A query was run and no Result Maps were found for the Mapped Statement '" + mappedStatement.getId()
297           + "'.  It's likely that neither a Result Type nor a Result Map was specified.");
298     }
299   }
300 
301   private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
302     try {
303       if (parentMapping != null) {
304         handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
305       } else {
306         if (resultHandler == null) {
307           DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
308           handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
309           multipleResults.add(defaultResultHandler.getResultList());
310         } else {
311           handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
312         }
313       }
314     } finally {
315       // issue #228 (close resultsets)
316       closeResultSet(rsw.getResultSet());
317     }
318   }
319 
320   @SuppressWarnings("unchecked")
321   private List<Object> collapseSingleResultList(List<Object> multipleResults) {
322     return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
323   }
324 
325   //
326   // HANDLE ROWS FOR SIMPLE RESULTMAP
327   //
328 
329   public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
330     if (resultMap.hasNestedResultMaps()) {
331       ensureNoRowBounds();
332       checkResultHandler();
333       handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
334     } else {
335       handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
336     }
337   }
338 
339   private void ensureNoRowBounds() {
340     if (configuration.isSafeRowBoundsEnabled() && rowBounds != null && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) {
341       throw new ExecutorException("Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. "
342           + "Use safeRowBoundsEnabled=false setting to bypass this check.");
343     }
344   }
345 
346   protected void checkResultHandler() {
347     if (resultHandler != null && configuration.isSafeResultHandlerEnabled() && !mappedStatement.isResultOrdered()) {
348       throw new ExecutorException("Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler. "
349           + "Use safeResultHandlerEnabled=false setting to bypass this check "
350           + "or ensure your statement returns ordered data and set resultOrdered=true on it.");
351     }
352   }
353 
354   private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
355       throws SQLException {
356     DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
357     ResultSet resultSet = rsw.getResultSet();
358     skipRows(resultSet, rowBounds);
359     while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
360       ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
361       Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
362       storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
363     }
364   }
365 
366   private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
367     if (parentMapping != null) {
368       linkToParents(rs, parentMapping, rowValue);
369     } else {
370       callResultHandler(resultHandler, resultContext, rowValue);
371     }
372   }
373 
374   @SuppressWarnings("unchecked" /* because ResultHandler<?> is always ResultHandler<Object>*/)
375   private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
376     resultContext.nextResultObject(rowValue);
377     ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
378   }
379 
380   private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
381     return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
382   }
383 
384   private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
385     if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
386       if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
387         rs.absolute(rowBounds.getOffset());
388       }
389     } else {
390       for (int i = 0; i < rowBounds.getOffset(); i++) {
391         if (!rs.next()) {
392           break;
393         }
394       }
395     }
396   }
397 
398   //
399   // GET VALUE FROM ROW FOR SIMPLE RESULT MAP
400   //
401 
402   private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
403     final ResultLoaderMap lazyLoader = new ResultLoaderMap();
404     Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
405     if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
406       final MetaObject metaObject = configuration.newMetaObject(rowValue);
407       boolean foundValues = this.useConstructorMappings;
408       if (shouldApplyAutomaticMappings(resultMap, false)) {
409         foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
410       }
411       foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
412       foundValues = lazyLoader.size() > 0 || foundValues;
413       rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
414     }
415     return rowValue;
416   }
417 
418   //
419   // GET VALUE FROM ROW FOR NESTED RESULT MAP
420   //
421 
422   private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
423     final String resultMapId = resultMap.getId();
424     Object rowValue = partialObject;
425     if (rowValue != null) {
426       final MetaObject metaObject = configuration.newMetaObject(rowValue);
427       putAncestor(rowValue, resultMapId);
428       applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
429       ancestorObjects.remove(resultMapId);
430     } else {
431       final ResultLoaderMap lazyLoader = new ResultLoaderMap();
432       rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
433       if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
434         final MetaObject metaObject = configuration.newMetaObject(rowValue);
435         boolean foundValues = this.useConstructorMappings;
436         if (shouldApplyAutomaticMappings(resultMap, true)) {
437           foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
438         }
439         foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
440         putAncestor(rowValue, resultMapId);
441         foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
442         ancestorObjects.remove(resultMapId);
443         foundValues = lazyLoader.size() > 0 || foundValues;
444         rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
445       }
446       if (combinedKey != CacheKey.NULL_CACHE_KEY) {
447         nestedResultObjects.put(combinedKey, rowValue);
448       }
449     }
450     return rowValue;
451   }
452 
453   private void putAncestor(Object resultObject, String resultMapId) {
454     ancestorObjects.put(resultMapId, resultObject);
455   }
456 
457   private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
458     if (resultMap.getAutoMapping() != null) {
459       return resultMap.getAutoMapping();
460     } else {
461       if (isNested) {
462         return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
463       } else {
464         return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
465       }
466     }
467   }
468 
469   //
470   // PROPERTY MAPPINGS
471   //
472 
473   private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
474       throws SQLException {
475     final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
476     boolean foundValues = false;
477     final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
478     for (ResultMapping propertyMapping : propertyMappings) {
479       String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
480       if (propertyMapping.getNestedResultMapId() != null) {
481         // the user added a column attribute to a nested result map, ignore it
482         column = null;
483       }
484       if (propertyMapping.isCompositeResult()
485           || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
486           || propertyMapping.getResultSet() != null) {
487         Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
488         // issue #541 make property optional
489         final String property = propertyMapping.getProperty();
490         if (property == null) {
491           continue;
492         } else if (value == DEFERRED) {
493           foundValues = true;
494           continue;
495         }
496         if (value != null) {
497           foundValues = true;
498         }
499         if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
500           // gcode issue #377, call setter on nulls (value is not 'found')
501           metaObject.setValue(property, value);
502         }
503       }
504     }
505     return foundValues;
506   }
507 
508   private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
509       throws SQLException {
510     if (propertyMapping.getNestedQueryId() != null) {
511       return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
512     } else if (propertyMapping.getResultSet() != null) {
513       addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
514       return DEFERRED;
515     } else {
516       final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
517       final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
518       return typeHandler.getResult(rs, column);
519     }
520   }
521 
522   private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
523     final String mapKey = resultMap.getId() + ":" + columnPrefix;
524     List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
525     if (autoMapping == null) {
526       autoMapping = new ArrayList<>();
527       final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
528       // Remove the entry to release the memory
529       List<String> mappedInConstructorAutoMapping = constructorAutoMappingColumns.remove(mapKey);
530       if (mappedInConstructorAutoMapping != null) {
531         unmappedColumnNames.removeAll(mappedInConstructorAutoMapping);
532       }
533       for (String columnName : unmappedColumnNames) {
534         String propertyName = columnName;
535         if (columnPrefix != null && !columnPrefix.isEmpty()) {
536           // When columnPrefix is specified,
537           // ignore columns without the prefix.
538           if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
539             propertyName = columnName.substring(columnPrefix.length());
540           } else {
541             continue;
542           }
543         }
544         final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
545         if (property != null && metaObject.hasSetter(property)) {
546           if (resultMap.getMappedProperties().contains(property)) {
547             continue;
548           }
549           final Class<?> propertyType = metaObject.getSetterType(property);
550           if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
551             final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
552             autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
553           } else {
554             configuration.getAutoMappingUnknownColumnBehavior()
555                 .doAction(mappedStatement, columnName, property, propertyType);
556           }
557         } else {
558           configuration.getAutoMappingUnknownColumnBehavior()
559               .doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
560         }
561       }
562       autoMappingsCache.put(mapKey, autoMapping);
563     }
564     return autoMapping;
565   }
566 
567   private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
568     List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
569     boolean foundValues = false;
570     if (!autoMapping.isEmpty()) {
571       for (UnMappedColumnAutoMapping mapping : autoMapping) {
572         final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
573         if (value != null) {
574           foundValues = true;
575         }
576         if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
577           // gcode issue #377, call setter on nulls (value is not 'found')
578           metaObject.setValue(mapping.property, value);
579         }
580       }
581     }
582     return foundValues;
583   }
584 
585   // MULTIPLE RESULT SETS
586 
587   private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException {
588     CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), parentMapping.getForeignColumn());
589     List<PendingRelation> parents = pendingRelations.get(parentKey);
590     if (parents != null) {
591       for (PendingRelation parent : parents) {
592         if (parent != null && rowValue != null) {
593           linkObjects(parent.metaObject, parent.propertyMapping, rowValue);
594         }
595       }
596     }
597   }
598 
599   private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, ResultMapping parentMapping) throws SQLException {
600     CacheKey cacheKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), parentMapping.getColumn());
601     PendingRelation deferLoad = new PendingRelation();
602     deferLoad.metaObject = metaResultObject;
603     deferLoad.propertyMapping = parentMapping;
604     List<PendingRelation> relations = MapUtil.computeIfAbsent(pendingRelations, cacheKey, k -> new ArrayList<>());
605     // issue #255
606     relations.add(deferLoad);
607     ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet());
608     if (previous == null) {
609       nextResultMaps.put(parentMapping.getResultSet(), parentMapping);
610     } else {
611       if (!previous.equals(parentMapping)) {
612         throw new ExecutorException("Two different properties are mapped to the same resultSet");
613       }
614     }
615   }
616 
617   private CacheKey createKeyForMultipleResults(ResultSet rs, ResultMapping resultMapping, String names, String columns) throws SQLException {
618     CacheKey cacheKey = new CacheKey();
619     cacheKey.update(resultMapping);
620     if (columns != null && names != null) {
621       String[] columnsArray = columns.split(",");
622       String[] namesArray = names.split(",");
623       for (int i = 0; i < columnsArray.length; i++) {
624         Object value = rs.getString(columnsArray[i]);
625         if (value != null) {
626           cacheKey.update(namesArray[i]);
627           cacheKey.update(value);
628         }
629       }
630     }
631     return cacheKey;
632   }
633 
634   //
635   // INSTANTIATION & CONSTRUCTOR MAPPING
636   //
637 
638   private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
639     this.useConstructorMappings = false; // reset previous mapping result
640     final List<Class<?>> constructorArgTypes = new ArrayList<>();
641     final List<Object> constructorArgs = new ArrayList<>();
642     Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
643     if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
644       final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
645       for (ResultMapping propertyMapping : propertyMappings) {
646         // issue gcode #109 && issue #149
647         if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
648           resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
649           break;
650         }
651       }
652     }
653     this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
654     return resultObject;
655   }
656 
657   private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
658       throws SQLException {
659     final Class<?> resultType = resultMap.getType();
660     final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
661     final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
662     if (hasTypeHandlerForResultObject(rsw, resultType)) {
663       return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
664     } else if (!constructorMappings.isEmpty()) {
665       return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
666     } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
667       return objectFactory.create(resultType);
668     } else if (shouldApplyAutomaticMappings(resultMap, false)) {
669       return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs);
670     }
671     throw new ExecutorException("Do not know how to create an instance of " + resultType);
672   }
673 
674   Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings,
675                                          List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) {
676     boolean foundValues = false;
677     for (ResultMapping constructorMapping : constructorMappings) {
678       final Class<?> parameterType = constructorMapping.getJavaType();
679       final String column = constructorMapping.getColumn();
680       final Object value;
681       try {
682         if (constructorMapping.getNestedQueryId() != null) {
683           value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
684         } else if (constructorMapping.getNestedResultMapId() != null) {
685           final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
686           value = getRowValue(rsw, resultMap, getColumnPrefix(columnPrefix, constructorMapping));
687         } else {
688           final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
689           value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
690         }
691       } catch (ResultMapException | SQLException e) {
692         throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
693       }
694       constructorArgTypes.add(parameterType);
695       constructorArgs.add(value);
696       foundValues = value != null || foundValues;
697     }
698     return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
699   }
700 
701   private Object createByConstructorSignature(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, Class<?> resultType,
702       List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
703     return applyConstructorAutomapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs,
704         findConstructorForAutomapping(resultType, rsw).orElseThrow(() -> new ExecutorException(
705             "No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames())));
706   }
707 
708   private Optional<Constructor<?>> findConstructorForAutomapping(final Class<?> resultType, ResultSetWrapper rsw) {
709     Constructor<?>[] constructors = resultType.getDeclaredConstructors();
710     if (constructors.length == 1) {
711       return Optional.of(constructors[0]);
712     }
713     for (final Constructor<?> constructor : constructors) {
714       if (constructor.isAnnotationPresent(AutomapConstructor.class)) {
715         return Optional.of(constructor);
716       }
717     }
718     if (configuration.isArgNameBasedConstructorAutoMapping()) {
719       // Finding-best-match type implementation is possible,
720       // but using @AutomapConstructor seems sufficient.
721       throw new ExecutorException(MessageFormat.format(
722           "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.",
723           resultType.getName()));
724     } else {
725       return Arrays.stream(constructors).filter(x -> findUsableConstructorByArgTypes(x, rsw.getJdbcTypes())).findAny();
726     }
727   }
728 
729   private boolean findUsableConstructorByArgTypes(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) {
730     final Class<?>[] parameterTypes = constructor.getParameterTypes();
731     if (parameterTypes.length != jdbcTypes.size()) {
732       return false;
733     }
734     for (int i = 0; i < parameterTypes.length; i++) {
735       if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) {
736         return false;
737       }
738     }
739     return true;
740   }
741 
742   private Object applyConstructorAutomapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor) throws SQLException {
743     boolean foundValues = false;
744     if (configuration.isArgNameBasedConstructorAutoMapping()) {
745       foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs,
746           constructor, foundValues);
747     } else {
748       foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor,
749           foundValues);
750     }
751     return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
752   }
753 
754   private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,
755       List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {
756     for (int i = 0; i < constructor.getParameterTypes().length; i++) {
757       Class<?> parameterType = constructor.getParameterTypes()[i];
758       String columnName = rsw.getColumnNames().get(i);
759       TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
760       Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
761       constructorArgTypes.add(parameterType);
762       constructorArgs.add(value);
763       foundValues = value != null || foundValues;
764     }
765     return foundValues;
766   }
767 
768   private boolean applyArgNameBasedConstructorAutoMapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, Class<?> resultType,
769       List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues)
770       throws SQLException {
771     List<String> missingArgs = null;
772     Parameter[] params = constructor.getParameters();
773     for (Parameter param : params) {
774       boolean columnNotFound = true;
775       Param paramAnno = param.getAnnotation(Param.class);
776       String paramName = paramAnno == null ? param.getName() : paramAnno.value();
777       for (String columnName : rsw.getColumnNames()) {
778         if (columnMatchesParam(columnName, paramName, columnPrefix)) {
779           Class<?> paramType = param.getType();
780           TypeHandler<?> typeHandler = rsw.getTypeHandler(paramType, columnName);
781           Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
782           constructorArgTypes.add(paramType);
783           constructorArgs.add(value);
784           final String mapKey = resultMap.getId() + ":" + columnPrefix;
785           if (!autoMappingsCache.containsKey(mapKey)) {
786             MapUtil.computeIfAbsent(constructorAutoMappingColumns, mapKey, k -> new ArrayList<>()).add(columnName);
787           }
788           columnNotFound = false;
789           foundValues = value != null || foundValues;
790         }
791       }
792       if (columnNotFound) {
793         if (missingArgs == null) {
794           missingArgs = new ArrayList<>();
795         }
796         missingArgs.add(paramName);
797       }
798     }
799     if (foundValues && constructorArgs.size() < params.length) {
800       throw new ExecutorException(MessageFormat.format("Constructor auto-mapping of ''{1}'' failed "
801           + "because ''{0}'' were not found in the result set; "
802           + "Available columns are ''{2}'' and mapUnderscoreToCamelCase is ''{3}''.",
803           missingArgs, constructor, rsw.getColumnNames(), configuration.isMapUnderscoreToCamelCase()));
804     }
805     return foundValues;
806   }
807 
808   private boolean columnMatchesParam(String columnName, String paramName, String columnPrefix) {
809     if (columnPrefix != null) {
810       if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
811         return false;
812       }
813       columnName = columnName.substring(columnPrefix.length());
814     }
815     return paramName
816         .equalsIgnoreCase(configuration.isMapUnderscoreToCamelCase() ? columnName.replace("_", "") : columnName);
817   }
818 
819   private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
820     final Class<?> resultType = resultMap.getType();
821     final String columnName;
822     if (!resultMap.getResultMappings().isEmpty()) {
823       final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
824       final ResultMapping mapping = resultMappingList.get(0);
825       columnName = prependPrefix(mapping.getColumn(), columnPrefix);
826     } else {
827       columnName = rsw.getColumnNames().get(0);
828     }
829     final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
830     return typeHandler.getResult(rsw.getResultSet(), columnName);
831   }
832 
833   //
834   // NESTED QUERY
835   //
836 
837   private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix) throws SQLException {
838     final String nestedQueryId = constructorMapping.getNestedQueryId();
839     final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
840     final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
841     final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping, nestedQueryParameterType, columnPrefix);
842     Object value = null;
843     if (nestedQueryParameterObject != null) {
844       final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
845       final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
846       final Class<?> targetType = constructorMapping.getJavaType();
847       final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
848       value = resultLoader.loadResult();
849     }
850     return value;
851   }
852 
853   private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
854       throws SQLException {
855     final String nestedQueryId = propertyMapping.getNestedQueryId();
856     final String property = propertyMapping.getProperty();
857     final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
858     final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
859     final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
860     Object value = null;
861     if (nestedQueryParameterObject != null) {
862       final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
863       final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
864       final Class<?> targetType = propertyMapping.getJavaType();
865       if (executor.isCached(nestedQuery, key)) {
866         executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
867         value = DEFERRED;
868       } else {
869         final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
870         if (propertyMapping.isLazy()) {
871           lazyLoader.addLoader(property, metaResultObject, resultLoader);
872           value = DEFERRED;
873         } else {
874           value = resultLoader.loadResult();
875         }
876       }
877     }
878     return value;
879   }
880 
881   private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
882     if (resultMapping.isCompositeResult()) {
883       return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix);
884     } else {
885       return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix);
886     }
887   }
888 
889   private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
890     final TypeHandler<?> typeHandler;
891     if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
892       typeHandler = typeHandlerRegistry.getTypeHandler(parameterType);
893     } else {
894       typeHandler = typeHandlerRegistry.getUnknownTypeHandler();
895     }
896     return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
897   }
898 
899   private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
900     final Object parameterObject = instantiateParameterObject(parameterType);
901     final MetaObject metaObject = configuration.newMetaObject(parameterObject);
902     boolean foundValues = false;
903     for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
904       final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty());
905       final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType);
906       final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix));
907       // issue #353 & #560 do not execute nested query if key is null
908       if (propValue != null) {
909         metaObject.setValue(innerResultMapping.getProperty(), propValue);
910         foundValues = true;
911       }
912     }
913     return foundValues ? parameterObject : null;
914   }
915 
916   private Object instantiateParameterObject(Class<?> parameterType) {
917     if (parameterType == null) {
918       return new HashMap<>();
919     } else if (ParamMap.class.equals(parameterType)) {
920       return new HashMap<>(); // issue #649
921     } else {
922       return objectFactory.create(parameterType);
923     }
924   }
925 
926   //
927   // DISCRIMINATOR
928   //
929 
930   public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException {
931     Set<String> pastDiscriminators = new HashSet<>();
932     Discriminator discriminator = resultMap.getDiscriminator();
933     while (discriminator != null) {
934       final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
935       final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
936       if (configuration.hasResultMap(discriminatedMapId)) {
937         resultMap = configuration.getResultMap(discriminatedMapId);
938         Discriminator lastDiscriminator = discriminator;
939         discriminator = resultMap.getDiscriminator();
940         if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
941           break;
942         }
943       } else {
944         break;
945       }
946     }
947     return resultMap;
948   }
949 
950   private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix) throws SQLException {
951     final ResultMapping resultMapping = discriminator.getResultMapping();
952     final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
953     return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
954   }
955 
956   private String prependPrefix(String columnName, String prefix) {
957     if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
958       return columnName;
959     }
960     return prefix + columnName;
961   }
962 
963   //
964   // HANDLE NESTED RESULT MAPS
965   //
966 
967   private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
968     final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
969     ResultSet resultSet = rsw.getResultSet();
970     skipRows(resultSet, rowBounds);
971     Object rowValue = previousRowValue;
972     while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
973       final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
974       final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
975       Object partialObject = nestedResultObjects.get(rowKey);
976       // issue #577 && #542
977       if (mappedStatement.isResultOrdered()) {
978         if (partialObject == null && rowValue != null) {
979           nestedResultObjects.clear();
980           storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
981         }
982         rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
983       } else {
984         rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
985         if (partialObject == null) {
986           storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
987         }
988       }
989     }
990     if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
991       storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
992       previousRowValue = null;
993     } else if (rowValue != null) {
994       previousRowValue = rowValue;
995     }
996   }
997 
998   //
999   // NESTED RESULT MAP (JOIN MAPPING)
1000   //
1001 
1002   private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {
1003     boolean foundValues = false;
1004     for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
1005       final String nestedResultMapId = resultMapping.getNestedResultMapId();
1006       if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
1007         try {
1008           final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
1009           final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
1010           if (resultMapping.getColumnPrefix() == null) {
1011             // try to fill circular reference only when columnPrefix
1012             // is not specified for the nested result map (issue #215)
1013             Object ancestorObject = ancestorObjects.get(nestedResultMapId);
1014             if (ancestorObject != null) {
1015               if (newObject) {
1016                 linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
1017               }
1018               continue;
1019             }
1020           }
1021           final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
1022           final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1023           Object rowValue = nestedResultObjects.get(combinedKey);
1024           boolean knownValue = rowValue != null;
1025           instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
1026           if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
1027             rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
1028             if (rowValue != null && !knownValue) {
1029               linkObjects(metaObject, resultMapping, rowValue);
1030               foundValues = true;
1031             }
1032           }
1033         } catch (SQLException e) {
1034           throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
1035         }
1036       }
1037     }
1038     return foundValues;
1039   }
1040 
1041   private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) {
1042     final StringBuilder columnPrefixBuilder = new StringBuilder();
1043     if (parentPrefix != null) {
1044       columnPrefixBuilder.append(parentPrefix);
1045     }
1046     if (resultMapping.getColumnPrefix() != null) {
1047       columnPrefixBuilder.append(resultMapping.getColumnPrefix());
1048     }
1049     return columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH);
1050   }
1051 
1052   private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSetWrapper rsw) throws SQLException {
1053     Set<String> notNullColumns = resultMapping.getNotNullColumns();
1054     if (notNullColumns != null && !notNullColumns.isEmpty()) {
1055       ResultSet rs = rsw.getResultSet();
1056       for (String column : notNullColumns) {
1057         rs.getObject(prependPrefix(column, columnPrefix));
1058         if (!rs.wasNull()) {
1059           return true;
1060         }
1061       }
1062       return false;
1063     } else if (columnPrefix != null) {
1064       for (String columnName : rsw.getColumnNames()) {
1065         if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix.toUpperCase(Locale.ENGLISH))) {
1066           return true;
1067         }
1068       }
1069       return false;
1070     }
1071     return true;
1072   }
1073 
1074   private ResultMap getNestedResultMap(ResultSet rs, String nestedResultMapId, String columnPrefix) throws SQLException {
1075     ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId);
1076     return resolveDiscriminatedResultMap(rs, nestedResultMap, columnPrefix);
1077   }
1078 
1079   //
1080   // UNIQUE RESULT KEY
1081   //
1082 
1083   private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
1084     final CacheKey cacheKey = new CacheKey();
1085     cacheKey.update(resultMap.getId());
1086     List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
1087     if (resultMappings.isEmpty()) {
1088       if (Map.class.isAssignableFrom(resultMap.getType())) {
1089         createRowKeyForMap(rsw, cacheKey);
1090       } else {
1091         createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
1092       }
1093     } else {
1094       createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
1095     }
1096     if (cacheKey.getUpdateCount() < 2) {
1097       return CacheKey.NULL_CACHE_KEY;
1098     }
1099     return cacheKey;
1100   }
1101 
1102   private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) {
1103     if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) {
1104       CacheKey combinedKey;
1105       try {
1106         combinedKey = rowKey.clone();
1107       } catch (CloneNotSupportedException e) {
1108         throw new ExecutorException("Error cloning cache key.  Cause: " + e, e);
1109       }
1110       combinedKey.update(parentRowKey);
1111       return combinedKey;
1112     }
1113     return CacheKey.NULL_CACHE_KEY;
1114   }
1115 
1116   private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {
1117     List<ResultMapping> resultMappings = resultMap.getIdResultMappings();
1118     if (resultMappings.isEmpty()) {
1119       resultMappings = resultMap.getPropertyResultMappings();
1120     }
1121     return resultMappings;
1122   }
1123 
1124   private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {
1125     for (ResultMapping resultMapping : resultMappings) {
1126       if (resultMapping.isSimple()) {
1127         final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
1128         final TypeHandler<?> th = resultMapping.getTypeHandler();
1129         List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
1130         // Issue #114
1131         if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
1132           final Object value = th.getResult(rsw.getResultSet(), column);
1133           if (value != null || configuration.isReturnInstanceForEmptyRow()) {
1134             cacheKey.update(column);
1135             cacheKey.update(value);
1136           }
1137         }
1138       }
1139     }
1140   }
1141 
1142   private void createRowKeyForUnmappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, String columnPrefix) throws SQLException {
1143     final MetaClass metaType = MetaClass.forClass(resultMap.getType(), reflectorFactory);
1144     List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
1145     for (String column : unmappedColumnNames) {
1146       String property = column;
1147       if (columnPrefix != null && !columnPrefix.isEmpty()) {
1148         // When columnPrefix is specified, ignore columns without the prefix.
1149         if (column.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1150           property = column.substring(columnPrefix.length());
1151         } else {
1152           continue;
1153         }
1154       }
1155       if (metaType.findProperty(property, configuration.isMapUnderscoreToCamelCase()) != null) {
1156         String value = rsw.getResultSet().getString(column);
1157         if (value != null) {
1158           cacheKey.update(column);
1159           cacheKey.update(value);
1160         }
1161       }
1162     }
1163   }
1164 
1165   private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException {
1166     List<String> columnNames = rsw.getColumnNames();
1167     for (String columnName : columnNames) {
1168       final String value = rsw.getResultSet().getString(columnName);
1169       if (value != null) {
1170         cacheKey.update(columnName);
1171         cacheKey.update(value);
1172       }
1173     }
1174   }
1175 
1176   private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
1177     final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1178     if (collectionProperty != null) {
1179       final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1180       targetMetaObject.add(rowValue);
1181     } else {
1182       metaObject.setValue(resultMapping.getProperty(), rowValue);
1183     }
1184   }
1185 
1186   private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
1187     final String propertyName = resultMapping.getProperty();
1188     Object propertyValue = metaObject.getValue(propertyName);
1189     if (propertyValue == null) {
1190       Class<?> type = resultMapping.getJavaType();
1191       if (type == null) {
1192         type = metaObject.getSetterType(propertyName);
1193       }
1194       try {
1195         if (objectFactory.isCollection(type)) {
1196           propertyValue = objectFactory.create(type);
1197           metaObject.setValue(propertyName, propertyValue);
1198           return propertyValue;
1199         }
1200       } catch (Exception e) {
1201         throw new ExecutorException("Error instantiating collection property for result '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
1202       }
1203     } else if (objectFactory.isCollection(propertyValue.getClass())) {
1204       return propertyValue;
1205     }
1206     return null;
1207   }
1208 
1209   private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
1210     if (rsw.getColumnNames().size() == 1) {
1211       return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0)));
1212     }
1213     return typeHandlerRegistry.hasTypeHandler(resultType);
1214   }
1215 
1216 }