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