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