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