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 Set<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 Class<?>[] parameterTypes = constructor.getParameterTypes();
786 for (int i = 0; i < parameterTypes.length; i++) {
787 Class<?> parameterType = parameterTypes[i];
788 String columnName = rsw.getColumnNames().get(i);
789 TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
790 Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
791 constructorArgTypes.add(parameterType);
792 constructorArgs.add(value);
793 foundValues = value != null || foundValues;
794 }
795 return foundValues;
796 }
797
798 private boolean applyArgNameBasedConstructorAutoMapping(ResultSetWrapper rsw, ResultMap resultMap,
799 String columnPrefix, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor,
800 boolean foundValues) throws SQLException {
801 List<String> missingArgs = null;
802 Parameter[] params = constructor.getParameters();
803 for (Parameter param : params) {
804 boolean columnNotFound = true;
805 Param paramAnno = param.getAnnotation(Param.class);
806 String paramName = paramAnno == null ? param.getName() : paramAnno.value();
807 for (String columnName : rsw.getColumnNames()) {
808 if (columnMatchesParam(columnName, paramName, columnPrefix)) {
809 Class<?> paramType = param.getType();
810 TypeHandler<?> typeHandler = rsw.getTypeHandler(paramType, columnName);
811 Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
812 constructorArgTypes.add(paramType);
813 constructorArgs.add(value);
814 final String mapKey = resultMap.getId() + ":" + columnPrefix;
815 if (!autoMappingsCache.containsKey(mapKey)) {
816 MapUtil.computeIfAbsent(constructorAutoMappingColumns, mapKey, k -> new ArrayList<>()).add(columnName);
817 }
818 columnNotFound = false;
819 foundValues = value != null || foundValues;
820 }
821 }
822 if (columnNotFound) {
823 if (missingArgs == null) {
824 missingArgs = new ArrayList<>();
825 }
826 missingArgs.add(paramName);
827 }
828 }
829 if (foundValues && constructorArgs.size() < params.length) {
830 throw new ExecutorException(MessageFormat.format(
831 "Constructor auto-mapping of ''{1}'' failed " + "because ''{0}'' were not found in the result set; "
832 + "Available columns are ''{2}'' and mapUnderscoreToCamelCase is ''{3}''.",
833 missingArgs, constructor, rsw.getColumnNames(), configuration.isMapUnderscoreToCamelCase()));
834 }
835 return foundValues;
836 }
837
838 private boolean columnMatchesParam(String columnName, String paramName, String columnPrefix) {
839 if (columnPrefix != null) {
840 if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
841 return false;
842 }
843 columnName = columnName.substring(columnPrefix.length());
844 }
845 return paramName
846 .equalsIgnoreCase(configuration.isMapUnderscoreToCamelCase() ? columnName.replace("_", "") : columnName);
847 }
848
849 private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
850 throws SQLException {
851 final Class<?> resultType = resultMap.getType();
852 final String columnName;
853 if (!resultMap.getResultMappings().isEmpty()) {
854 final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
855 final ResultMapping mapping = resultMappingList.get(0);
856 columnName = prependPrefix(mapping.getColumn(), columnPrefix);
857 } else {
858 columnName = rsw.getColumnNames().get(0);
859 }
860 final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
861 return typeHandler.getResult(rsw.getResultSet(), columnName);
862 }
863
864
865
866
867
868 private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)
869 throws SQLException {
870 final String nestedQueryId = constructorMapping.getNestedQueryId();
871 final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
872 final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
873 final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping,
874 nestedQueryParameterType, columnPrefix);
875 Object value = null;
876 if (nestedQueryParameterObject != null) {
877 final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
878 final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
879 nestedBoundSql);
880 final Class<?> targetType = constructorMapping.getJavaType();
881 final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
882 nestedQueryParameterObject, targetType, key, nestedBoundSql);
883 value = resultLoader.loadResult();
884 }
885 return value;
886 }
887
888 private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
889 ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
890 final String nestedQueryId = propertyMapping.getNestedQueryId();
891 final String property = propertyMapping.getProperty();
892 final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
893 final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
894 final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping,
895 nestedQueryParameterType, columnPrefix);
896 Object value = null;
897 if (nestedQueryParameterObject != null) {
898 final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
899 final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
900 nestedBoundSql);
901 final Class<?> targetType = propertyMapping.getJavaType();
902 if (executor.isCached(nestedQuery, key)) {
903 executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
904 value = DEFERRED;
905 } else {
906 final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
907 nestedQueryParameterObject, targetType, key, nestedBoundSql);
908 if (propertyMapping.isLazy()) {
909 lazyLoader.addLoader(property, metaResultObject, resultLoader);
910 value = DEFERRED;
911 } else {
912 value = resultLoader.loadResult();
913 }
914 }
915 }
916 return value;
917 }
918
919 private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
920 String columnPrefix) throws SQLException {
921 if (resultMapping.isCompositeResult()) {
922 return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix);
923 }
924 return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix);
925 }
926
927 private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
928 String columnPrefix) throws SQLException {
929 final TypeHandler<?> typeHandler;
930 if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
931 typeHandler = typeHandlerRegistry.getTypeHandler(parameterType);
932 } else {
933 typeHandler = typeHandlerRegistry.getUnknownTypeHandler();
934 }
935 return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
936 }
937
938 private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
939 String columnPrefix) throws SQLException {
940 final Object parameterObject = instantiateParameterObject(parameterType);
941 final MetaObject metaObject = configuration.newMetaObject(parameterObject);
942 boolean foundValues = false;
943 for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
944 final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty());
945 final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType);
946 final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix));
947
948 if (propValue != null) {
949 metaObject.setValue(innerResultMapping.getProperty(), propValue);
950 foundValues = true;
951 }
952 }
953 return foundValues ? parameterObject : null;
954 }
955
956 private Object instantiateParameterObject(Class<?> parameterType) {
957 if (parameterType == null) {
958 return new HashMap<>();
959 }
960 if (ParamMap.class.equals(parameterType)) {
961 return new HashMap<>();
962 } else {
963 return objectFactory.create(parameterType);
964 }
965 }
966
967
968
969
970
971 public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)
972 throws SQLException {
973 Set<String> pastDiscriminators = new HashSet<>();
974 Discriminator discriminator = resultMap.getDiscriminator();
975 while (discriminator != null) {
976 final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
977 final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
978 if (!configuration.hasResultMap(discriminatedMapId)) {
979 break;
980 }
981 resultMap = configuration.getResultMap(discriminatedMapId);
982 Discriminator lastDiscriminator = discriminator;
983 discriminator = resultMap.getDiscriminator();
984 if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
985 break;
986 }
987 }
988 return resultMap;
989 }
990
991 private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix)
992 throws SQLException {
993 final ResultMapping resultMapping = discriminator.getResultMapping();
994 final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
995 return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
996 }
997
998 private String prependPrefix(String columnName, String prefix) {
999 if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
1000 return columnName;
1001 }
1002 return prefix + columnName;
1003 }
1004
1005
1006
1007
1008
1009 private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap,
1010 ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
1011 final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
1012 ResultSet resultSet = rsw.getResultSet();
1013 skipRows(resultSet, rowBounds);
1014 Object rowValue = previousRowValue;
1015 while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
1016 final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
1017 final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
1018 Object partialObject = nestedResultObjects.get(rowKey);
1019
1020 if (mappedStatement.isResultOrdered()) {
1021 if (partialObject == null && rowValue != null) {
1022 nestedResultObjects.clear();
1023 storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1024 }
1025 rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1026 } else {
1027 rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1028 if (partialObject == null) {
1029 storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1030 }
1031 }
1032 }
1033 if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
1034 storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1035 previousRowValue = null;
1036 } else if (rowValue != null) {
1037 previousRowValue = rowValue;
1038 }
1039 }
1040
1041
1042
1043
1044
1045 private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
1046 String parentPrefix, CacheKey parentRowKey, boolean newObject) {
1047 boolean foundValues = false;
1048 for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
1049 final String nestedResultMapId = resultMapping.getNestedResultMapId();
1050 if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
1051 try {
1052 final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
1053 final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
1054 if (resultMapping.getColumnPrefix() == null) {
1055
1056
1057 Object ancestorObject = ancestorObjects.get(nestedResultMapId);
1058 if (ancestorObject != null) {
1059 if (newObject) {
1060 linkObjects(metaObject, resultMapping, ancestorObject);
1061 }
1062 continue;
1063 }
1064 }
1065 final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
1066 final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1067 Object rowValue = nestedResultObjects.get(combinedKey);
1068 boolean knownValue = rowValue != null;
1069 instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1070 if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
1071 rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
1072 if (rowValue != null && !knownValue) {
1073 linkObjects(metaObject, resultMapping, rowValue);
1074 foundValues = true;
1075 }
1076 }
1077 } catch (SQLException e) {
1078 throw new ExecutorException(
1079 "Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + e, e);
1080 }
1081 }
1082 }
1083 return foundValues;
1084 }
1085
1086 private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) {
1087 final StringBuilder columnPrefixBuilder = new StringBuilder();
1088 if (parentPrefix != null) {
1089 columnPrefixBuilder.append(parentPrefix);
1090 }
1091 if (resultMapping.getColumnPrefix() != null) {
1092 columnPrefixBuilder.append(resultMapping.getColumnPrefix());
1093 }
1094 return columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH);
1095 }
1096
1097 private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSetWrapper rsw)
1098 throws SQLException {
1099 Set<String> notNullColumns = resultMapping.getNotNullColumns();
1100 if (notNullColumns != null && !notNullColumns.isEmpty()) {
1101 ResultSet rs = rsw.getResultSet();
1102 for (String column : notNullColumns) {
1103 rs.getObject(prependPrefix(column, columnPrefix));
1104 if (!rs.wasNull()) {
1105 return true;
1106 }
1107 }
1108 return false;
1109 }
1110 if (columnPrefix != null) {
1111 for (String columnName : rsw.getColumnNames()) {
1112 if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix.toUpperCase(Locale.ENGLISH))) {
1113 return true;
1114 }
1115 }
1116 return false;
1117 }
1118 return true;
1119 }
1120
1121 private ResultMap getNestedResultMap(ResultSet rs, String nestedResultMapId, String columnPrefix)
1122 throws SQLException {
1123 ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId);
1124 return resolveDiscriminatedResultMap(rs, nestedResultMap, columnPrefix);
1125 }
1126
1127
1128
1129
1130
1131 private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
1132 final CacheKey cacheKey = new CacheKey();
1133 cacheKey.update(resultMap.getId());
1134 List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
1135 if (resultMappings.isEmpty()) {
1136 if (Map.class.isAssignableFrom(resultMap.getType())) {
1137 createRowKeyForMap(rsw, cacheKey);
1138 } else {
1139 createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
1140 }
1141 } else {
1142 createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
1143 }
1144 if (cacheKey.getUpdateCount() < 2) {
1145 return CacheKey.NULL_CACHE_KEY;
1146 }
1147 return cacheKey;
1148 }
1149
1150 private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) {
1151 if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) {
1152 CacheKey combinedKey;
1153 try {
1154 combinedKey = rowKey.clone();
1155 } catch (CloneNotSupportedException e) {
1156 throw new ExecutorException("Error cloning cache key. Cause: " + e, e);
1157 }
1158 combinedKey.update(parentRowKey);
1159 return combinedKey;
1160 }
1161 return CacheKey.NULL_CACHE_KEY;
1162 }
1163
1164 private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {
1165 List<ResultMapping> resultMappings = resultMap.getIdResultMappings();
1166 if (resultMappings.isEmpty()) {
1167 resultMappings = resultMap.getPropertyResultMappings();
1168 }
1169 return resultMappings;
1170 }
1171
1172 private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1173 List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {
1174 for (ResultMapping resultMapping : resultMappings) {
1175 if (resultMapping.isSimple()) {
1176 final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
1177 final TypeHandler<?> th = resultMapping.getTypeHandler();
1178 Set<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
1179
1180 if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
1181 final Object value = th.getResult(rsw.getResultSet(), column);
1182 if (value != null || configuration.isReturnInstanceForEmptyRow()) {
1183 cacheKey.update(column);
1184 cacheKey.update(value);
1185 }
1186 }
1187 }
1188 }
1189 }
1190
1191 private void createRowKeyForUnmappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1192 String columnPrefix) throws SQLException {
1193 final MetaClass metaType = MetaClass.forClass(resultMap.getType(), reflectorFactory);
1194 List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
1195 for (String column : unmappedColumnNames) {
1196 String property = column;
1197 if (columnPrefix != null && !columnPrefix.isEmpty()) {
1198
1199 if (!column.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1200 continue;
1201 }
1202 property = column.substring(columnPrefix.length());
1203 }
1204 if (metaType.findProperty(property, configuration.isMapUnderscoreToCamelCase()) != null) {
1205 String value = rsw.getResultSet().getString(column);
1206 if (value != null) {
1207 cacheKey.update(column);
1208 cacheKey.update(value);
1209 }
1210 }
1211 }
1212 }
1213
1214 private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException {
1215 List<String> columnNames = rsw.getColumnNames();
1216 for (String columnName : columnNames) {
1217 final String value = rsw.getResultSet().getString(columnName);
1218 if (value != null) {
1219 cacheKey.update(columnName);
1220 cacheKey.update(value);
1221 }
1222 }
1223 }
1224
1225 private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
1226 final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1227 if (collectionProperty != null) {
1228 final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1229 targetMetaObject.add(rowValue);
1230 } else {
1231 metaObject.setValue(resultMapping.getProperty(), rowValue);
1232 }
1233 }
1234
1235 private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
1236 final String propertyName = resultMapping.getProperty();
1237 Object propertyValue = metaObject.getValue(propertyName);
1238 if (propertyValue == null) {
1239 Class<?> type = resultMapping.getJavaType();
1240 if (type == null) {
1241 type = metaObject.getSetterType(propertyName);
1242 }
1243 try {
1244 if (objectFactory.isCollection(type)) {
1245 propertyValue = objectFactory.create(type);
1246 metaObject.setValue(propertyName, propertyValue);
1247 return propertyValue;
1248 }
1249 } catch (Exception e) {
1250 throw new ExecutorException(
1251 "Error instantiating collection property for result '" + resultMapping.getProperty() + "'. Cause: " + e,
1252 e);
1253 }
1254 } else if (objectFactory.isCollection(propertyValue.getClass())) {
1255 return propertyValue;
1256 }
1257 return null;
1258 }
1259
1260 private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
1261 if (rsw.getColumnNames().size() == 1) {
1262 return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0)));
1263 }
1264 return typeHandlerRegistry.hasTypeHandler(resultType);
1265 }
1266
1267 }