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