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