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