1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
62
63
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
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
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
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
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
159 closeResultSet(rs);
160 }
161 }
162
163
164
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
208
209 if (stmt.getMoreResults()) {
210 rs = stmt.getResultSet();
211 } else {
212 if (stmt.getUpdateCount() == -1) {
213
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
223 try {
224 if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
225
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
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
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
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
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" )
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
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
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
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
406 final String property = propertyMapping.getProperty();
407
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);
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
445
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
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
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
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
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
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
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
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
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
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
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
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
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);
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);
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
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
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
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
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 }