• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mybatis / mybatis-3 / 2673

25 Jan 2025 11:34PM UTC coverage: 87.155% (-0.1%) from 87.27%
2673

Pull #3388

github

web-flow
Merge 0e0798685 into 1b2346c8b
Pull Request #3388: Support nested cursor

3622 of 4421 branches covered (81.93%)

11 of 31 new or added lines in 2 files covered. (35.48%)

2 existing lines in 1 file now uncovered.

9560 of 10969 relevant lines covered (87.15%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.79
/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java
1
/*
2
 *    Copyright 2009-2025 the original author or authors.
3
 *
4
 *    Licensed under the Apache License, Version 2.0 (the "License");
5
 *    you may not use this file except in compliance with the License.
6
 *    You may obtain a copy of the License at
7
 *
8
 *       https://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *    Unless required by applicable law or agreed to in writing, software
11
 *    distributed under the License is distributed on an "AS IS" BASIS,
12
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *    See the License for the specific language governing permissions and
14
 *    limitations under the License.
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.Collection;
28
import java.util.HashMap;
29
import java.util.HashSet;
30
import java.util.IdentityHashMap;
31
import java.util.List;
32
import java.util.Locale;
33
import java.util.Map;
34
import java.util.Optional;
35
import java.util.Set;
36

37
import org.apache.ibatis.annotations.AutomapConstructor;
38
import org.apache.ibatis.annotations.Param;
39
import org.apache.ibatis.binding.MapperMethod.ParamMap;
40
import org.apache.ibatis.cache.CacheKey;
41
import org.apache.ibatis.cursor.Cursor;
42
import org.apache.ibatis.cursor.defaults.DefaultCursor;
43
import org.apache.ibatis.executor.ErrorContext;
44
import org.apache.ibatis.executor.Executor;
45
import org.apache.ibatis.executor.ExecutorException;
46
import org.apache.ibatis.executor.loader.ResultLoader;
47
import org.apache.ibatis.executor.loader.ResultLoaderMap;
48
import org.apache.ibatis.executor.parameter.ParameterHandler;
49
import org.apache.ibatis.executor.result.DefaultResultContext;
50
import org.apache.ibatis.executor.result.DefaultResultHandler;
51
import org.apache.ibatis.executor.result.ResultMapException;
52
import org.apache.ibatis.mapping.BoundSql;
53
import org.apache.ibatis.mapping.Discriminator;
54
import org.apache.ibatis.mapping.MappedStatement;
55
import org.apache.ibatis.mapping.ParameterMapping;
56
import org.apache.ibatis.mapping.ParameterMode;
57
import org.apache.ibatis.mapping.ResultMap;
58
import org.apache.ibatis.mapping.ResultMapping;
59
import org.apache.ibatis.reflection.MetaClass;
60
import org.apache.ibatis.reflection.MetaObject;
61
import org.apache.ibatis.reflection.ReflectorFactory;
62
import org.apache.ibatis.reflection.factory.ObjectFactory;
63
import org.apache.ibatis.session.AutoMappingBehavior;
64
import org.apache.ibatis.session.Configuration;
65
import org.apache.ibatis.session.ResultContext;
66
import org.apache.ibatis.session.ResultHandler;
67
import org.apache.ibatis.session.RowBounds;
68
import org.apache.ibatis.type.JdbcType;
69
import org.apache.ibatis.type.TypeHandler;
70
import org.apache.ibatis.type.TypeHandlerRegistry;
71

72
/**
73
 * @author Clinton Begin
74
 * @author Eduardo Macarron
75
 * @author Iwao AVE!
76
 * @author Kazuki Shimizu
77
 * @author Willie Scholtz
78
 */
79
public class DefaultResultSetHandler implements ResultSetHandler {
80

81
  private static final Object DEFERRED = new Object();
1✔
82

83
  private final Executor executor;
84
  private final Configuration configuration;
85
  private final MappedStatement mappedStatement;
86
  private final RowBounds rowBounds;
87
  private final ParameterHandler parameterHandler;
88
  private final ResultHandler<?> resultHandler;
89
  private final BoundSql boundSql;
90
  private final TypeHandlerRegistry typeHandlerRegistry;
91
  private final ObjectFactory objectFactory;
92
  private final ReflectorFactory reflectorFactory;
93

94
  // pending creations property tracker
95
  private final Map<Object, PendingRelation> pendingPccRelations = new IdentityHashMap<>();
1✔
96

97
  // nested resultmaps
98
  private final Map<CacheKey, Object> nestedResultObjects = new HashMap<>();
1✔
99
  private final Map<String, Object> ancestorObjects = new HashMap<>();
1✔
100
  private Object previousRowValue;
101

102
  // multiple resultsets
103
  private final Map<String, ResultMapping> nextResultMaps = new HashMap<>();
1✔
104
  private final Map<CacheKey, List<PendingRelation>> pendingRelations = new HashMap<>();
1✔
105

106
  // Cached Automappings
107
  private final Map<String, List<UnMappedColumnAutoMapping>> autoMappingsCache = new HashMap<>();
1✔
108
  private final Map<String, List<String>> constructorAutoMappingColumns = new HashMap<>();
1✔
109

110
  // temporary marking flag that indicate using constructor mapping (use field to reduce memory usage)
111
  private boolean useConstructorMappings;
112

113
  private static class PendingRelation {
114
    public MetaObject metaObject;
115
    public ResultMapping propertyMapping;
116
  }
117

118
  private static class UnMappedColumnAutoMapping {
119
    private final String column;
120
    private final String property;
121
    private final TypeHandler<?> typeHandler;
122
    private final boolean primitive;
123

124
    public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
1✔
125
      this.column = column;
1✔
126
      this.property = property;
1✔
127
      this.typeHandler = typeHandler;
1✔
128
      this.primitive = primitive;
1✔
129
    }
1✔
130
  }
131

132
  public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler,
133
      ResultHandler<?> resultHandler, BoundSql boundSql, RowBounds rowBounds) {
1✔
134
    this.executor = executor;
1✔
135
    this.configuration = mappedStatement.getConfiguration();
1✔
136
    this.mappedStatement = mappedStatement;
1✔
137
    this.rowBounds = rowBounds;
1✔
138
    this.parameterHandler = parameterHandler;
1✔
139
    this.boundSql = boundSql;
1✔
140
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
1✔
141
    this.objectFactory = configuration.getObjectFactory();
1✔
142
    this.reflectorFactory = configuration.getReflectorFactory();
1✔
143
    this.resultHandler = resultHandler;
1✔
144
  }
1✔
145

146
  //
147
  // HANDLE OUTPUT PARAMETER
148
  //
149

150
  @Override
151
  public void handleOutputParameters(CallableStatement cs) throws SQLException {
152
    final Object parameterObject = parameterHandler.getParameterObject();
1✔
153
    final MetaObject metaParam = configuration.newMetaObject(parameterObject);
1✔
154
    final List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
1✔
155
    for (int i = 0; i < parameterMappings.size(); i++) {
1✔
156
      final ParameterMapping parameterMapping = parameterMappings.get(i);
1✔
157
      if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) {
1!
158
        if (ResultSet.class.equals(parameterMapping.getJavaType())) {
1!
159
          handleRefCursorOutputParameter((ResultSet) cs.getObject(i + 1), parameterMapping, metaParam);
×
160
        } else {
161
          final TypeHandler<?> typeHandler = parameterMapping.getTypeHandler();
1✔
162
          metaParam.setValue(parameterMapping.getProperty(), typeHandler.getResult(cs, i + 1));
1✔
163
        }
164
      }
165
    }
166
  }
1✔
167

168
  private void handleRefCursorOutputParameter(ResultSet rs, ParameterMapping parameterMapping, MetaObject metaParam)
169
      throws SQLException {
170
    if (rs == null) {
×
171
      return;
×
172
    }
173
    try {
174
      final String resultMapId = parameterMapping.getResultMapId();
×
175
      final ResultMap resultMap = configuration.getResultMap(resultMapId);
×
176
      final ResultSetWrapper rsw = new ResultSetWrapper(rs, configuration);
×
177
      if (this.resultHandler == null) {
×
178
        final DefaultResultHandler resultHandler = new DefaultResultHandler(objectFactory);
×
179
        handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
×
180
        metaParam.setValue(parameterMapping.getProperty(), resultHandler.getResultList());
×
181
      } else {
×
182
        handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
×
183
      }
184
    } finally {
185
      // issue #228 (close resultsets)
186
      closeResultSet(rs);
×
187
    }
188
  }
×
189

190
  //
191
  // HANDLE RESULT SETS
192
  //
193
  @Override
194
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
195
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
1✔
196

197
    final List<Object> multipleResults = new ArrayList<>();
1✔
198

199
    int resultSetCount = 0;
1✔
200
    ResultSetWrapper rsw = getFirstResultSet(stmt);
1✔
201

202
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
1✔
203
    int resultMapCount = resultMaps.size();
1✔
204
    validateResultMapsCount(rsw, resultMapCount);
1✔
205
    while (rsw != null && resultMapCount > resultSetCount) {
1✔
206
      ResultMap resultMap = resultMaps.get(resultSetCount);
1✔
207
      handleResultSet(rsw, resultMap, multipleResults, null);
1✔
208
      rsw = getNextResultSet(stmt);
1✔
209
      cleanUpAfterHandlingResultSet();
1✔
210
      resultSetCount++;
1✔
211
    }
1✔
212

213
    String[] resultSets = mappedStatement.getResultSets();
1✔
214
    if (resultSets != null) {
1✔
215
      while (rsw != null && resultSetCount < resultSets.length) {
1!
216
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
1✔
217
        if (parentMapping != null) {
1!
218
          String nestedResultMapId = parentMapping.getNestedResultMapId();
1✔
219
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
1✔
220
          handleResultSet(rsw, resultMap, null, parentMapping);
1✔
221
        }
222
        rsw = getNextResultSet(stmt);
1✔
223
        cleanUpAfterHandlingResultSet();
1✔
224
        resultSetCount++;
1✔
225
      }
1✔
226
    }
227

228
    return collapseSingleResultList(multipleResults);
1✔
229
  }
230

231
  @Override
232
  public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException {
233
    ErrorContext.instance().activity("handling cursor results").object(mappedStatement.getId());
1✔
234

235
    ResultSetWrapper rsw = getFirstResultSet(stmt);
1✔
236

237
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
1✔
238

239
    int resultMapCount = resultMaps.size();
1✔
240
    validateResultMapsCount(rsw, resultMapCount);
1✔
241
    if (resultMapCount != 1) {
1!
242
      throw new ExecutorException("Cursor results cannot be mapped to multiple resultMaps");
×
243
    }
244

245
    ResultMap resultMap = resultMaps.get(0);
1✔
246
    return new DefaultCursor<>(this, resultMap, rsw, rowBounds);
1✔
247
  }
248

249
  private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
250
    ResultSet rs = null;
1✔
251
    SQLException e1 = null;
1✔
252

253
    try {
254
      rs = stmt.getResultSet();
1✔
255
    } catch (SQLException e) {
×
256
      // Oracle throws ORA-17283 for implicit cursor
257
      e1 = e;
×
258
    }
1✔
259

260
    try {
261
      while (rs == null) {
1✔
262
        // move forward to get the first resultset in case the driver
263
        // doesn't return the resultset as the first result (HSQLDB)
264
        if (stmt.getMoreResults()) {
1!
265
          rs = stmt.getResultSet();
×
266
        } else if (stmt.getUpdateCount() == -1) {
1!
267
          // no more results. Must be no resultset
268
          break;
1✔
269
        }
270
      }
271
    } catch (SQLException e) {
×
272
      throw e1 != null ? e1 : e;
×
273
    }
1✔
274

275
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
1✔
276
  }
277

278
  private ResultSetWrapper getNextResultSet(Statement stmt) {
279
    // Making this method tolerant of bad JDBC drivers
280
    try {
281
      // We stopped checking DatabaseMetaData#supportsMultipleResultSets()
282
      // because Oracle driver (incorrectly) returns false
283

284
      // Crazy Standard JDBC way of determining if there are more results
285
      // DO NOT try to 'improve' the condition even if IDE tells you to!
286
      // It's important that getUpdateCount() is called here.
287
      if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
1✔
288
        ResultSet rs = stmt.getResultSet();
1✔
289
        if (rs == null) {
1!
290
          return getNextResultSet(stmt);
×
291
        } else {
292
          return new ResultSetWrapper(rs, configuration);
1✔
293
        }
294
      }
295
    } catch (Exception e) {
×
296
      // Intentionally ignored.
297
    }
1✔
298
    return null;
1✔
299
  }
300

301
  private void closeResultSet(ResultSet rs) {
302
    try {
303
      if (rs != null) {
1!
304
        rs.close();
1✔
305
      }
306
    } catch (SQLException e) {
×
307
      // ignore
308
    }
1✔
309
  }
1✔
310

311
  private void cleanUpAfterHandlingResultSet() {
312
    nestedResultObjects.clear();
1✔
313
  }
1✔
314

315
  private void validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount) {
316
    if (rsw != null && resultMapCount < 1) {
1✔
317
      throw new ExecutorException(
1✔
318
          "A query was run and no Result Maps were found for the Mapped Statement '" + mappedStatement.getId()
1✔
319
              + "'. 'resultType' or 'resultMap' must be specified when there is no corresponding method.");
320
    }
321
  }
1✔
322

323
  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults,
324
      ResultMapping parentMapping) throws SQLException {
325
    try {
326
      if (parentMapping != null) {
1✔
327
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
1✔
328
      } else if (resultHandler == null) {
1✔
329
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
1✔
330
        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
1✔
331
        multipleResults.add(defaultResultHandler.getResultList());
1✔
332
      } else {
1✔
333
        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
1✔
334
      }
335
    } finally {
336
      // issue #228 (close resultsets)
337
      closeResultSet(rsw.getResultSet());
1✔
338
    }
339
  }
1✔
340

341
  @SuppressWarnings("unchecked")
342
  private List<Object> collapseSingleResultList(List<Object> multipleResults) {
343
    return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
1✔
344
  }
345

346
  //
347
  // HANDLE ROWS FOR SIMPLE RESULTMAP
348
  //
349

350
  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
351
      RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
352
    if (resultMap.hasNestedResultMaps()) {
1✔
353
      ensureNoRowBounds();
1✔
354
      checkResultHandler();
1✔
355
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
1✔
356
    } else {
357
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
1✔
358
    }
359
  }
1✔
360

361
  private void ensureNoRowBounds() {
362
    if (configuration.isSafeRowBoundsEnabled() && rowBounds != null
1!
363
        && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) {
×
364
      throw new ExecutorException(
×
365
          "Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. "
366
              + "Use safeRowBoundsEnabled=false setting to bypass this check.");
367
    }
368
  }
1✔
369

370
  protected void checkResultHandler() {
371
    if (resultHandler != null && configuration.isSafeResultHandlerEnabled() && !mappedStatement.isResultOrdered()) {
1!
372
      throw new ExecutorException(
1✔
373
          "Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler. "
374
              + "Use safeResultHandlerEnabled=false setting to bypass this check "
375
              + "or ensure your statement returns ordered data and set resultOrdered=true on it.");
376
    }
377
  }
1✔
378

379
  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
380
      ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
381
    final boolean useCollectionConstructorInjection = resultMap.hasResultMapsUsingConstructorCollection();
1✔
382

383
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
1✔
384
    ResultSet resultSet = rsw.getResultSet();
1✔
385
    skipRows(resultSet, rowBounds);
1✔
386
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
1!
387
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
1✔
388
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null, null);
1✔
389
      if (!useCollectionConstructorInjection) {
1!
390
        storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
391
      } else {
392
        if (!(rowValue instanceof PendingConstructorCreation)) {
×
393
          throw new ExecutorException("Expected result object to be a pending constructor creation!");
×
394
        }
395

396
        createAndStorePendingCreation(resultHandler, resultSet, resultContext, (PendingConstructorCreation) rowValue);
×
397
      }
398
    }
1✔
399
  }
1✔
400

401
  private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue,
402
      ResultMapping parentMapping, ResultSet rs) throws SQLException {
403
    if (parentMapping != null) {
1✔
404
      linkToParents(rs, parentMapping, rowValue);
1✔
405
      return;
1✔
406
    }
407

408
    if (pendingPccRelations.containsKey(rowValue)) {
1✔
409
      createPendingConstructorCreations(rowValue);
1✔
410
    }
411

412
    callResultHandler(resultHandler, resultContext, rowValue);
1✔
413
  }
1✔
414

415
  @SuppressWarnings("unchecked" /* because ResultHandler<?> is always ResultHandler<Object> */)
416
  private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext,
417
      Object rowValue) {
418
    resultContext.nextResultObject(rowValue);
1✔
419
    ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
1✔
420
  }
1✔
421

422
  private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
423
    return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
1✔
424
  }
425

426
  private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
427
    if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
1✔
428
      if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
1!
429
        rs.absolute(rowBounds.getOffset());
1✔
430
      }
431
    } else {
432
      for (int i = 0; i < rowBounds.getOffset(); i++) {
1✔
433
        if (!rs.next()) {
1!
434
          break;
×
435
        }
436
      }
437
    }
438
  }
1✔
439

440
  //
441
  // GET VALUE FROM ROW FOR SIMPLE RESULT MAP
442
  //
443

444
  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, CacheKey parentRowKey)
445
      throws SQLException {
446
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
1✔
447
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix, parentRowKey);
1✔
448
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
1✔
449
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
1✔
450
      boolean foundValues = this.useConstructorMappings;
1✔
451
      if (shouldApplyAutomaticMappings(resultMap, false)) {
1✔
452
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
1✔
453
      }
454
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
1✔
455
      foundValues = lazyLoader.size() > 0 || foundValues;
1✔
456
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
1✔
457
    }
458

459
    if (parentRowKey != null) {
1✔
460
      // found a simple object/primitive in pending constructor creation that will need linking later
461
      final CacheKey rowKey = createRowKey(resultMap, rsw, columnPrefix);
1✔
462
      final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
463

464
      if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
465
        nestedResultObjects.put(combinedKey, rowValue);
1✔
466
      }
467
    }
468

469
    return rowValue;
1✔
470
  }
471

472
  //
473
  // GET VALUE FROM ROW FOR NESTED RESULT MAP
474
  //
475

476
  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix,
477
      Object partialObject) throws SQLException {
478
    final String resultMapId = resultMap.getId();
1✔
479
    Object rowValue = partialObject;
1✔
480
    if (rowValue != null) {
1✔
481
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
1✔
482
      putAncestor(rowValue, resultMapId);
1✔
483
      applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
1✔
484
      ancestorObjects.remove(resultMapId);
1✔
485
    } else {
1✔
486
      final ResultLoaderMap lazyLoader = new ResultLoaderMap();
1✔
487
      rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix, combinedKey);
1✔
488
      if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
1✔
489
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
1✔
490
        boolean foundValues = this.useConstructorMappings;
1✔
491
        if (shouldApplyAutomaticMappings(resultMap, true)) {
1✔
492
          foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
1!
493
        }
494
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
1✔
495
        putAncestor(rowValue, resultMapId);
1✔
496
        foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true)
1✔
497
            || foundValues;
498
        ancestorObjects.remove(resultMapId);
1✔
499
        foundValues = lazyLoader.size() > 0 || foundValues;
1✔
500
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
1✔
501
      }
502
      if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
503
        nestedResultObjects.put(combinedKey, rowValue);
1✔
504
      }
505
    }
506
    return rowValue;
1✔
507
  }
508

509
  private void putAncestor(Object resultObject, String resultMapId) {
510
    ancestorObjects.put(resultMapId, resultObject);
1✔
511
  }
1✔
512

513
  private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
514
    if (resultMap.getAutoMapping() != null) {
1✔
515
      return resultMap.getAutoMapping();
1✔
516
    }
517
    if (isNested) {
1✔
518
      return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
1✔
519
    } else {
520
      return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
1✔
521
    }
522
  }
523

524
  //
525
  // PROPERTY MAPPINGS
526
  //
527

528
  private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
529
      ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
530
    final Set<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
1✔
531
    boolean foundValues = false;
1✔
532
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
1✔
533
    for (ResultMapping propertyMapping : propertyMappings) {
1✔
534
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
1✔
535
      if (propertyMapping.getNestedResultMapId() != null && !JdbcType.CURSOR.equals(propertyMapping.getJdbcType())) {
1!
536
        // the user added a column attribute to a nested result map, ignore it
537
        column = null;
1✔
538
      }
539
      if (propertyMapping.isCompositeResult()
1✔
540
          || column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))
1✔
541
          || propertyMapping.getResultSet() != null) {
1✔
542
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader,
1✔
543
            columnPrefix);
544
        // issue #541 make property optional
545
        final String property = propertyMapping.getProperty();
1✔
546
        if (property == null) {
1✔
547
          continue;
1✔
548
        }
549
        if (value == DEFERRED) {
1✔
550
          foundValues = true;
1✔
551
          continue;
1✔
552
        }
553
        if (value != null) {
1✔
554
          foundValues = true;
1✔
555
        }
556
        if (value != null
1✔
557
            || configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()) {
1!
558
          // gcode issue #377, call setter on nulls (value is not 'found')
559
          metaObject.setValue(property, value);
1✔
560
        }
561
      }
562
    }
1✔
563
    return foundValues;
1✔
564
  }
565

566
  private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
567
      ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
568
    if (propertyMapping.getNestedQueryId() != null) {
1✔
569
      return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
1✔
570
    }
571
    if (JdbcType.CURSOR.equals(propertyMapping.getJdbcType())) {
1!
NEW
572
      List<Object> results = getNestedCursorValue(rs, propertyMapping, columnPrefix);
×
NEW
573
      linkObjects(metaResultObject, propertyMapping, results.get(0), true);
×
NEW
574
      return metaResultObject.getValue(propertyMapping.getProperty());
×
575
    }
576
    if (propertyMapping.getResultSet() != null) {
1✔
577
      addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
1✔
578
      return DEFERRED;
1✔
579
    } else {
580
      final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
1✔
581
      final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
1✔
582
      return typeHandler.getResult(rs, column);
1✔
583
    }
584
  }
585

586
  private List<Object> getNestedCursorValue(ResultSet rs, ResultMapping propertyMapping, String parentColumnPrefix)
587
      throws SQLException {
NEW
588
    final String column = prependPrefix(propertyMapping.getColumn(), parentColumnPrefix);
×
NEW
589
    ResultMap nestedResultMap = resolveDiscriminatedResultMap(rs,
×
NEW
590
        configuration.getResultMap(propertyMapping.getNestedResultMapId()),
×
NEW
591
        getColumnPrefix(parentColumnPrefix, propertyMapping));
×
NEW
592
    ResultSetWrapper rsw = new ResultSetWrapper(rs.getObject(column, ResultSet.class), configuration);
×
NEW
593
    List<Object> results = new ArrayList<>();
×
NEW
594
    handleResultSet(rsw, nestedResultMap, results, null);
×
NEW
595
    return results;
×
596
  }
597

598
  private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap,
599
      MetaObject metaObject, String columnPrefix) throws SQLException {
600
    final String mapKey = resultMap.getId() + ":" + columnPrefix;
1✔
601
    List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
1✔
602
    if (autoMapping == null) {
1✔
603
      autoMapping = new ArrayList<>();
1✔
604
      final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
1✔
605
      // Remove the entry to release the memory
606
      List<String> mappedInConstructorAutoMapping = constructorAutoMappingColumns.remove(mapKey);
1✔
607
      if (mappedInConstructorAutoMapping != null) {
1✔
608
        unmappedColumnNames.removeAll(mappedInConstructorAutoMapping);
1✔
609
      }
610
      for (String columnName : unmappedColumnNames) {
1✔
611
        String propertyName = columnName;
1✔
612
        if (columnPrefix != null && !columnPrefix.isEmpty()) {
1!
613
          // When columnPrefix is specified,
614
          // ignore columns without the prefix.
615
          if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
616
            continue;
1✔
617
          }
618
          propertyName = columnName.substring(columnPrefix.length());
1✔
619
        }
620
        final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
1✔
621
        if (property != null && metaObject.hasSetter(property)) {
1✔
622
          if (resultMap.getMappedProperties().contains(property)) {
1✔
623
            continue;
1✔
624
          }
625
          final Class<?> propertyType = metaObject.getSetterType(property);
1✔
626
          if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
1✔
627
            final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
1✔
628
            autoMapping
1✔
629
                .add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
1✔
630
          } else {
1✔
631
            configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property,
1✔
632
                propertyType);
633
          }
634
        } else {
1✔
635
          configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName,
1✔
636
              property != null ? property : propertyName, null);
1✔
637
        }
638
      }
1✔
639
      autoMappingsCache.put(mapKey, autoMapping);
1✔
640
    }
641
    return autoMapping;
1✔
642
  }
643

644
  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
645
      String columnPrefix) throws SQLException {
646
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
1✔
647
    boolean foundValues = false;
1✔
648
    if (!autoMapping.isEmpty()) {
1✔
649
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
1✔
650
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
1✔
651
        if (value != null) {
1✔
652
          foundValues = true;
1✔
653
        }
654
        if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) {
1!
655
          // gcode issue #377, call setter on nulls (value is not 'found')
656
          metaObject.setValue(mapping.property, value);
1✔
657
        }
658
      }
1✔
659
    }
660
    return foundValues;
1✔
661
  }
662

663
  // MULTIPLE RESULT SETS
664

665
  private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException {
666
    CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(),
1✔
667
        parentMapping.getForeignColumn());
1✔
668
    List<PendingRelation> parents = pendingRelations.get(parentKey);
1✔
669
    if (parents != null) {
1✔
670
      for (PendingRelation parent : parents) {
1✔
671
        if (parent != null && rowValue != null) {
1!
672
          linkObjects(parent.metaObject, parent.propertyMapping, rowValue);
1✔
673
        }
674
      }
1✔
675
    }
676
  }
1✔
677

678
  private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, ResultMapping parentMapping)
679
      throws SQLException {
680
    CacheKey cacheKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(),
1✔
681
        parentMapping.getColumn());
1✔
682
    PendingRelation deferLoad = new PendingRelation();
1✔
683
    deferLoad.metaObject = metaResultObject;
1✔
684
    deferLoad.propertyMapping = parentMapping;
1✔
685
    List<PendingRelation> relations = pendingRelations.computeIfAbsent(cacheKey, k -> new ArrayList<>());
1✔
686
    // issue #255
687
    relations.add(deferLoad);
1✔
688
    ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet());
1✔
689
    if (previous == null) {
1✔
690
      nextResultMaps.put(parentMapping.getResultSet(), parentMapping);
1✔
691
    } else if (!previous.equals(parentMapping)) {
1!
692
      throw new ExecutorException("Two different properties are mapped to the same resultSet");
×
693
    }
694
  }
1✔
695

696
  private CacheKey createKeyForMultipleResults(ResultSet rs, ResultMapping resultMapping, String names, String columns)
697
      throws SQLException {
698
    CacheKey cacheKey = new CacheKey();
1✔
699
    cacheKey.update(resultMapping);
1✔
700
    if (columns != null && names != null) {
1!
701
      String[] columnsArray = columns.split(",");
1✔
702
      String[] namesArray = names.split(",");
1✔
703
      for (int i = 0; i < columnsArray.length; i++) {
1✔
704
        Object value = rs.getString(columnsArray[i]);
1✔
705
        if (value != null) {
1!
706
          cacheKey.update(namesArray[i]);
1✔
707
          cacheKey.update(value);
1✔
708
        }
709
      }
710
    }
711
    return cacheKey;
1✔
712
  }
713

714
  //
715
  // INSTANTIATION & CONSTRUCTOR MAPPING
716
  //
717

718
  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,
719
      String columnPrefix, CacheKey parentRowKey) throws SQLException {
720
    this.useConstructorMappings = false; // reset previous mapping result
1✔
721
    final List<Class<?>> constructorArgTypes = new ArrayList<>();
1✔
722
    final List<Object> constructorArgs = new ArrayList<>();
1✔
723

724
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix,
1✔
725
        parentRowKey);
726
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
1✔
727
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
1✔
728
      for (ResultMapping propertyMapping : propertyMappings) {
1✔
729
        // issue gcode #109 && issue #149
730
        if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
1✔
731
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration,
1✔
732
              objectFactory, constructorArgTypes, constructorArgs);
733
          break;
1✔
734
        }
735
      }
1✔
736

737
      // (issue #101)
738
      if (resultMap.hasResultMapsUsingConstructorCollection() && resultObject instanceof PendingConstructorCreation) {
1!
739
        linkNestedPendingCreations(rsw, resultMap, columnPrefix, parentRowKey,
1✔
740
            (PendingConstructorCreation) resultObject, constructorArgs);
741
      }
742
    }
743

744
    this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
1✔
745
    return resultObject;
1✔
746
  }
747

748
  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
749
      List<Object> constructorArgs, String columnPrefix, CacheKey parentRowKey) throws SQLException {
750

751
    final Class<?> resultType = resultMap.getType();
1✔
752
    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
1✔
753
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
1✔
754
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
1✔
755
      return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
1✔
756
    }
757
    if (!constructorMappings.isEmpty()) {
1✔
758
      return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs,
1✔
759
          columnPrefix, resultMap.hasResultMapsUsingConstructorCollection(), parentRowKey);
1✔
760
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
1✔
761
      return objectFactory.create(resultType);
1✔
762
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
1!
763
      return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes,
1✔
764
          constructorArgs);
765
    }
766
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
×
767
  }
768

769
  Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType,
770
      List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
771
      String columnPrefix, boolean useCollectionConstructorInjection, CacheKey parentRowKey) {
772
    boolean foundValues = false;
1✔
773

774
    for (ResultMapping constructorMapping : constructorMappings) {
1✔
775
      final Class<?> parameterType = constructorMapping.getJavaType();
1✔
776
      final String column = constructorMapping.getColumn();
1✔
777
      final Object value;
778
      try {
779
        if (constructorMapping.getNestedQueryId() != null) {
1✔
780
          value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
1✔
781
        } else if (JdbcType.CURSOR.equals(constructorMapping.getJdbcType())) {
1!
NEW
782
          List<?> result = (List<?>) getNestedCursorValue(rsw.getResultSet(), constructorMapping, columnPrefix).get(0);
×
NEW
783
          if (objectFactory.isCollection(parameterType)) {
×
NEW
784
            MetaObject collection = configuration.newMetaObject(objectFactory.create(parameterType));
×
NEW
785
            collection.addAll((List<?>) result);
×
NEW
786
            value = collection.getOriginalObject();
×
NEW
787
          } else {
×
NEW
788
            value = toSingleObj(result);
×
789
          }
790
        } else if (constructorMapping.getNestedResultMapId() != null) {
1✔
791
          final String constructorColumnPrefix = getColumnPrefix(columnPrefix, constructorMapping);
1✔
792
          final ResultMap resultMap = resolveDiscriminatedResultMap(rsw.getResultSet(),
1✔
793
              configuration.getResultMap(constructorMapping.getNestedResultMapId()), constructorColumnPrefix);
1✔
794
          value = getRowValue(rsw, resultMap, constructorColumnPrefix,
1✔
795
              useCollectionConstructorInjection ? parentRowKey : null);
1✔
796
        } else {
1✔
797
          final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
1✔
798
          value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
1✔
799
        }
800
      } catch (ResultMapException | SQLException e) {
1✔
801
        throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
1✔
802
      }
1✔
803

804
      constructorArgTypes.add(parameterType);
1✔
805
      constructorArgs.add(value);
1✔
806

807
      foundValues = value != null || foundValues;
1✔
808
    }
1✔
809

810
    if (!foundValues) {
1✔
811
      return null;
1✔
812
    }
813

814
    if (useCollectionConstructorInjection) {
1✔
815
      // at least one of the nestedResultMaps contained a collection, we have to defer until later
816
      return new PendingConstructorCreation(resultType, constructorArgTypes, constructorArgs);
1✔
817
    }
818

819
    return objectFactory.create(resultType, constructorArgTypes, constructorArgs);
1✔
820
  }
821

822
  private Object createByConstructorSignature(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
823
      Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
824
    return applyConstructorAutomapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs,
1✔
825
        findConstructorForAutomapping(resultType, rsw).orElseThrow(() -> new ExecutorException(
1✔
826
            "No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames())));
×
827
  }
828

829
  private Optional<Constructor<?>> findConstructorForAutomapping(final Class<?> resultType, ResultSetWrapper rsw) {
830
    Constructor<?>[] constructors = resultType.getDeclaredConstructors();
1✔
831
    if (constructors.length == 1) {
1✔
832
      return Optional.of(constructors[0]);
1✔
833
    }
834
    Optional<Constructor<?>> annotated = Arrays.stream(constructors)
1✔
835
        .filter(x -> x.isAnnotationPresent(AutomapConstructor.class)).reduce((x, y) -> {
1✔
836
          throw new ExecutorException("@AutomapConstructor should be used in only one constructor.");
1✔
837
        });
838
    if (annotated.isPresent()) {
1✔
839
      return annotated;
1✔
840
    }
841
    if (configuration.isArgNameBasedConstructorAutoMapping()) {
1!
842
      // Finding-best-match type implementation is possible,
843
      // but using @AutomapConstructor seems sufficient.
844
      throw new ExecutorException(MessageFormat.format(
×
845
          "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.",
846
          resultType.getName()));
×
847
    } else {
848
      return Arrays.stream(constructors).filter(x -> findUsableConstructorByArgTypes(x, rsw.getJdbcTypes())).findAny();
1✔
849
    }
850
  }
851

852
  private boolean findUsableConstructorByArgTypes(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) {
853
    final Class<?>[] parameterTypes = constructor.getParameterTypes();
1✔
854
    if (parameterTypes.length != jdbcTypes.size()) {
1✔
855
      return false;
1✔
856
    }
857
    for (int i = 0; i < parameterTypes.length; i++) {
1✔
858
      if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) {
1!
859
        return false;
×
860
      }
861
    }
862
    return true;
1✔
863
  }
864

865
  private Object applyConstructorAutomapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
866
      Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor)
867
      throws SQLException {
868
    boolean foundValues = false;
1✔
869
    if (configuration.isArgNameBasedConstructorAutoMapping()) {
1✔
870
      foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, constructorArgTypes,
1✔
871
          constructorArgs, constructor, foundValues);
872
    } else {
873
      foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor,
1✔
874
          foundValues);
875
    }
876
    return foundValues || configuration.isReturnInstanceForEmptyRow()
1!
877
        ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
1✔
878
  }
879

880
  private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,
881
      List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {
882
    Class<?>[] parameterTypes = constructor.getParameterTypes();
1✔
883

884
    if (parameterTypes.length > rsw.getClassNames().size()) {
1✔
885
      throw new ExecutorException(MessageFormat.format(
1✔
886
          "Constructor auto-mapping of ''{0}'' failed. The constructor takes ''{1}'' arguments, but there are only ''{2}'' columns in the result set.",
887
          constructor, parameterTypes.length, rsw.getClassNames().size()));
1✔
888
    }
889

890
    for (int i = 0; i < parameterTypes.length; i++) {
1✔
891
      Class<?> parameterType = parameterTypes[i];
1✔
892
      String columnName = rsw.getColumnNames().get(i);
1✔
893
      TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
1✔
894
      Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
895
      constructorArgTypes.add(parameterType);
1✔
896
      constructorArgs.add(value);
1✔
897
      foundValues = value != null || foundValues;
1!
898
    }
899
    return foundValues;
1✔
900
  }
901

902
  private boolean applyArgNameBasedConstructorAutoMapping(ResultSetWrapper rsw, ResultMap resultMap,
903
      String columnPrefix, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor,
904
      boolean foundValues) throws SQLException {
905
    List<String> missingArgs = null;
1✔
906
    Parameter[] params = constructor.getParameters();
1✔
907
    for (Parameter param : params) {
1✔
908
      boolean columnNotFound = true;
1✔
909
      Param paramAnno = param.getAnnotation(Param.class);
1✔
910
      String paramName = paramAnno == null ? param.getName() : paramAnno.value();
1✔
911
      for (String columnName : rsw.getColumnNames()) {
1✔
912
        if (columnMatchesParam(columnName, paramName, columnPrefix)) {
1✔
913
          Class<?> paramType = param.getType();
1✔
914
          TypeHandler<?> typeHandler = rsw.getTypeHandler(paramType, columnName);
1✔
915
          Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
916
          constructorArgTypes.add(paramType);
1✔
917
          constructorArgs.add(value);
1✔
918
          final String mapKey = resultMap.getId() + ":" + columnPrefix;
1✔
919
          if (!autoMappingsCache.containsKey(mapKey)) {
1!
920
            constructorAutoMappingColumns.computeIfAbsent(mapKey, k -> new ArrayList<>()).add(columnName);
1✔
921
          }
922
          columnNotFound = false;
1✔
923
          foundValues = value != null || foundValues;
1!
924
        }
925
      }
1✔
926
      if (columnNotFound) {
1✔
927
        if (missingArgs == null) {
1!
928
          missingArgs = new ArrayList<>();
1✔
929
        }
930
        missingArgs.add(paramName);
1✔
931
      }
932
    }
933
    if (foundValues && constructorArgs.size() < params.length) {
1✔
934
      throw new ExecutorException(MessageFormat.format(
1✔
935
          "Constructor auto-mapping of ''{1}'' failed " + "because ''{0}'' were not found in the result set; "
936
              + "Available columns are ''{2}'' and mapUnderscoreToCamelCase is ''{3}''.",
937
          missingArgs, constructor, rsw.getColumnNames(), configuration.isMapUnderscoreToCamelCase()));
1✔
938
    }
939
    return foundValues;
1✔
940
  }
941

942
  private boolean columnMatchesParam(String columnName, String paramName, String columnPrefix) {
943
    if (columnPrefix != null) {
1✔
944
      if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
945
        return false;
1✔
946
      }
947
      columnName = columnName.substring(columnPrefix.length());
1✔
948
    }
949
    return paramName
1✔
950
        .equalsIgnoreCase(configuration.isMapUnderscoreToCamelCase() ? columnName.replace("_", "") : columnName);
1✔
951
  }
952

953
  private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
954
      throws SQLException {
955
    final Class<?> resultType = resultMap.getType();
1✔
956
    final String columnName;
957
    if (!resultMap.getResultMappings().isEmpty()) {
1✔
958
      final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
1✔
959
      final ResultMapping mapping = resultMappingList.get(0);
1✔
960
      columnName = prependPrefix(mapping.getColumn(), columnPrefix);
1✔
961
    } else {
1✔
962
      columnName = rsw.getColumnNames().get(0);
1✔
963
    }
964
    final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
1✔
965
    return typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
966
  }
967

968
  //
969
  // NESTED QUERY
970
  //
971

972
  private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)
973
      throws SQLException {
974
    final String nestedQueryId = constructorMapping.getNestedQueryId();
1✔
975
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
1✔
976
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
1✔
977
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping,
1✔
978
        nestedQueryParameterType, columnPrefix);
979
    Object value = null;
1✔
980
    if (nestedQueryParameterObject != null) {
1!
981
      final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
1✔
982
      final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
1✔
983
          nestedBoundSql);
984
      final Class<?> targetType = constructorMapping.getJavaType();
1✔
985
      final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
1✔
986
          nestedQueryParameterObject, targetType, key, nestedBoundSql);
987
      value = resultLoader.loadResult();
1✔
988
    }
989
    return value;
1✔
990
  }
991

992
  private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
993
      ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
994
    final String nestedQueryId = propertyMapping.getNestedQueryId();
1✔
995
    final String property = propertyMapping.getProperty();
1✔
996
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
1✔
997
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
1✔
998
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping,
1✔
999
        nestedQueryParameterType, columnPrefix);
1000
    Object value = null;
1✔
1001
    if (nestedQueryParameterObject != null) {
1✔
1002
      final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
1✔
1003
      final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
1✔
1004
          nestedBoundSql);
1005
      final Class<?> targetType = propertyMapping.getJavaType();
1✔
1006
      if (executor.isCached(nestedQuery, key)) {
1✔
1007
        executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
1✔
1008
        value = DEFERRED;
1✔
1009
      } else {
1010
        final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
1✔
1011
            nestedQueryParameterObject, targetType, key, nestedBoundSql);
1012
        if (propertyMapping.isLazy()) {
1✔
1013
          lazyLoader.addLoader(property, metaResultObject, resultLoader);
1✔
1014
          value = DEFERRED;
1✔
1015
        } else {
1016
          value = resultLoader.loadResult();
1✔
1017
        }
1018
      }
1019
    }
1020
    return value;
1✔
1021
  }
1022

1023
  private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
1024
      String columnPrefix) throws SQLException {
1025
    if (resultMapping.isCompositeResult()) {
1✔
1026
      return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix);
1✔
1027
    }
1028
    return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix);
1✔
1029
  }
1030

1031
  private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
1032
      String columnPrefix) throws SQLException {
1033
    final TypeHandler<?> typeHandler;
1034
    if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
1✔
1035
      typeHandler = typeHandlerRegistry.getTypeHandler(parameterType);
1✔
1036
    } else {
1037
      typeHandler = typeHandlerRegistry.getUnknownTypeHandler();
1✔
1038
    }
1039
    return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
1✔
1040
  }
1041

1042
  private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
1043
      String columnPrefix) throws SQLException {
1044
    final Object parameterObject = instantiateParameterObject(parameterType);
1✔
1045
    final MetaObject metaObject = configuration.newMetaObject(parameterObject);
1✔
1046
    boolean foundValues = false;
1✔
1047
    for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
1✔
1048
      final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty());
1✔
1049
      final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType);
1✔
1050
      final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix));
1✔
1051
      // issue #353 & #560 do not execute nested query if key is null
1052
      if (propValue != null) {
1✔
1053
        metaObject.setValue(innerResultMapping.getProperty(), propValue);
1✔
1054
        foundValues = true;
1✔
1055
      }
1056
    }
1✔
1057
    return foundValues ? parameterObject : null;
1✔
1058
  }
1059

1060
  private Object instantiateParameterObject(Class<?> parameterType) {
1061
    if (parameterType == null) {
1✔
1062
      return new HashMap<>();
1✔
1063
    }
1064
    if (ParamMap.class.equals(parameterType)) {
1✔
1065
      return new HashMap<>(); // issue #649
1✔
1066
    } else {
1067
      return objectFactory.create(parameterType);
1✔
1068
    }
1069
  }
1070

1071
  //
1072
  // DISCRIMINATOR
1073
  //
1074

1075
  public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)
1076
      throws SQLException {
1077
    Set<String> pastDiscriminators = new HashSet<>();
1✔
1078
    Discriminator discriminator = resultMap.getDiscriminator();
1✔
1079
    while (discriminator != null) {
1✔
1080
      final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
1✔
1081
      final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
1✔
1082
      if (!configuration.hasResultMap(discriminatedMapId)) {
1✔
1083
        break;
1✔
1084
      }
1085
      resultMap = configuration.getResultMap(discriminatedMapId);
1✔
1086
      Discriminator lastDiscriminator = discriminator;
1✔
1087
      discriminator = resultMap.getDiscriminator();
1✔
1088
      if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
1!
1089
        break;
1✔
1090
      }
1091
    }
1✔
1092
    return resultMap;
1✔
1093
  }
1094

1095
  private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix)
1096
      throws SQLException {
1097
    final ResultMapping resultMapping = discriminator.getResultMapping();
1✔
1098
    final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
1✔
1099
    return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
1✔
1100
  }
1101

1102
  private String prependPrefix(String columnName, String prefix) {
1103
    if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
1!
1104
      return columnName;
1✔
1105
    }
1106
    return prefix + columnName;
1✔
1107
  }
1108

1109
  //
1110
  // HANDLE NESTED RESULT MAPS
1111
  //
1112

1113
  private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap,
1114
      ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
1115
    final boolean useCollectionConstructorInjection = resultMap.hasResultMapsUsingConstructorCollection();
1✔
1116
    PendingConstructorCreation lastHandledCreation = null;
1✔
1117
    if (useCollectionConstructorInjection) {
1✔
1118
      verifyPendingCreationPreconditions(parentMapping);
1✔
1119
    }
1120

1121
    final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
1✔
1122
    ResultSet resultSet = rsw.getResultSet();
1✔
1123
    skipRows(resultSet, rowBounds);
1✔
1124
    Object rowValue = previousRowValue;
1✔
1125

1126
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
1!
1127
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
1✔
1128
      final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
1✔
1129

1130
      final Object partialObject = nestedResultObjects.get(rowKey);
1✔
1131
      final boolean foundNewUniqueRow = partialObject == null;
1✔
1132

1133
      // issue #577, #542 && #101
1134
      if (useCollectionConstructorInjection) {
1✔
1135
        if (foundNewUniqueRow && lastHandledCreation != null) {
1✔
1136
          createAndStorePendingCreation(resultHandler, resultSet, resultContext, lastHandledCreation);
1✔
1137
          lastHandledCreation = null;
1✔
1138
        }
1139

1140
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1141
        if (rowValue instanceof PendingConstructorCreation) {
1!
1142
          lastHandledCreation = (PendingConstructorCreation) rowValue;
1✔
1143
        }
1144
      } else if (mappedStatement.isResultOrdered()) {
1✔
1145
        if (foundNewUniqueRow && rowValue != null) {
1✔
1146
          nestedResultObjects.clear();
1✔
1147
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1148
        }
1149
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1150
      } else {
1151
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1152
        if (foundNewUniqueRow) {
1✔
1153
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1154
        }
1155
      }
1156
    }
1✔
1157

1158
    if (useCollectionConstructorInjection && lastHandledCreation != null) {
1!
1159
      createAndStorePendingCreation(resultHandler, resultSet, resultContext, lastHandledCreation);
1✔
1160
    } else if (rowValue != null && mappedStatement.isResultOrdered()
1✔
1161
        && shouldProcessMoreRows(resultContext, rowBounds)) {
1✔
1162
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1163
      previousRowValue = null;
1✔
1164
    } else if (rowValue != null) {
1✔
1165
      previousRowValue = rowValue;
1✔
1166
    }
1167
  }
1✔
1168

1169
  //
1170
  // NESTED RESULT MAP (PENDING CONSTRUCTOR CREATIONS)
1171
  //
1172
  private void linkNestedPendingCreations(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
1173
      CacheKey parentRowKey, PendingConstructorCreation pendingCreation, List<Object> constructorArgs)
1174
      throws SQLException {
1175
    if (parentRowKey == null) {
1!
1176
      // nothing to link, possibly due to simple (non-nested) result map
1177
      return;
×
1178
    }
1179

1180
    final CacheKey rowKey = createRowKey(resultMap, rsw, columnPrefix);
1✔
1181
    final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
1182

1183
    if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1!
1184
      nestedResultObjects.put(combinedKey, pendingCreation);
1✔
1185
    }
1186

1187
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
1✔
1188
    for (int index = 0; index < constructorMappings.size(); index++) {
1✔
1189
      final ResultMapping constructorMapping = constructorMappings.get(index);
1✔
1190
      final String nestedResultMapId = constructorMapping.getNestedResultMapId();
1✔
1191

1192
      if (nestedResultMapId == null) {
1✔
1193
        continue;
1✔
1194
      }
1195

1196
      final Class<?> javaType = constructorMapping.getJavaType();
1✔
1197
      if (javaType == null || !objectFactory.isCollection(javaType)) {
1!
1198
        continue;
1✔
1199
      }
1200

1201
      final String constructorColumnPrefix = getColumnPrefix(columnPrefix, constructorMapping);
1✔
1202
      final ResultMap nestedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(),
1✔
1203
          configuration.getResultMap(constructorMapping.getNestedResultMapId()), constructorColumnPrefix);
1✔
1204

1205
      final Object actualValue = constructorArgs.get(index);
1✔
1206
      final boolean hasValue = actualValue != null;
1✔
1207
      final boolean isInnerCreation = actualValue instanceof PendingConstructorCreation;
1✔
1208
      final boolean alreadyCreatedCollection = hasValue && objectFactory.isCollection(actualValue.getClass());
1!
1209

1210
      if (!isInnerCreation) {
1✔
1211
        final Collection<Object> value = pendingCreation.initializeCollectionForResultMapping(objectFactory,
1✔
1212
            nestedResultMap, constructorMapping, index);
1✔
1213
        if (!alreadyCreatedCollection) {
1!
1214
          // override values with empty collection
1215
          constructorArgs.set(index, value);
1✔
1216
        }
1217

1218
        // since we are linking a new value, we need to let nested objects know we did that
1219
        final CacheKey nestedRowKey = createRowKey(nestedResultMap, rsw, constructorColumnPrefix);
1✔
1220
        final CacheKey nestedCombinedKey = combineKeys(nestedRowKey, combinedKey);
1✔
1221

1222
        if (nestedCombinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
1223
          nestedResultObjects.put(nestedCombinedKey, pendingCreation);
1✔
1224
        }
1225

1226
        if (hasValue) {
1✔
1227
          pendingCreation.linkCollectionValue(constructorMapping, actualValue);
1✔
1228
        }
1229
      } else {
1✔
1230
        final PendingConstructorCreation innerCreation = (PendingConstructorCreation) actualValue;
1✔
1231
        final Collection<Object> value = pendingCreation.initializeCollectionForResultMapping(objectFactory,
1✔
1232
            nestedResultMap, constructorMapping, index);
1✔
1233
        // we will fill this collection when building the final object
1234
        constructorArgs.set(index, value);
1✔
1235
        // link the creation for building later
1236
        pendingCreation.linkCreation(constructorMapping, innerCreation);
1✔
1237
      }
1238
    }
1239
  }
1✔
1240

1241
  private boolean applyNestedPendingConstructorCreations(ResultSetWrapper rsw, ResultMap resultMap,
1242
      MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject, boolean foundValues) {
1243
    if (newObject) {
1✔
1244
      // new objects are linked by createResultObject
1245
      return false;
1✔
1246
    }
1247

1248
    for (ResultMapping constructorMapping : resultMap.getConstructorResultMappings()) {
1✔
1249
      final String nestedResultMapId = constructorMapping.getNestedResultMapId();
1✔
1250
      final Class<?> parameterType = constructorMapping.getJavaType();
1✔
1251
      if (nestedResultMapId == null || constructorMapping.getResultSet() != null || parameterType == null
1!
1252
          || !objectFactory.isCollection(parameterType)) {
1✔
1253
        continue;
1✔
1254
      }
1255

1256
      try {
1257
        final String columnPrefix = getColumnPrefix(parentPrefix, constructorMapping);
1✔
1258
        final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
1✔
1259

1260
        final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
1✔
1261
        final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
1262

1263
        // should have inserted already as a nested result object
1264
        Object rowValue = nestedResultObjects.get(combinedKey);
1✔
1265

1266
        PendingConstructorCreation pendingConstructorCreation = null;
1✔
1267
        if (rowValue instanceof PendingConstructorCreation) {
1✔
1268
          pendingConstructorCreation = (PendingConstructorCreation) rowValue;
1✔
1269
        } else if (rowValue != null) {
1✔
1270
          // found a simple object that was already linked/handled
1271
          continue;
1✔
1272
        }
1273

1274
        final boolean newValueForNestedResultMap = pendingConstructorCreation == null;
1✔
1275
        if (newValueForNestedResultMap) {
1✔
1276
          final Object parentObject = metaObject.getOriginalObject();
1✔
1277
          if (!(parentObject instanceof PendingConstructorCreation)) {
1!
1278
            throw new ExecutorException(
×
1279
                "parentObject is not a pending creation, cannot continue linking! MyBatis internal error!");
1280
          }
1281

1282
          pendingConstructorCreation = (PendingConstructorCreation) parentObject;
1✔
1283
        }
1284

1285
        rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix,
1✔
1286
            newValueForNestedResultMap ? null : pendingConstructorCreation);
1✔
1287

1288
        if (rowValue == null) {
1✔
1289
          continue;
1✔
1290
        }
1291

1292
        if (rowValue instanceof PendingConstructorCreation) {
1✔
1293
          if (newValueForNestedResultMap) {
1✔
1294
            // we created a brand new pcc. this is a new collection value
1295
            pendingConstructorCreation.linkCreation(constructorMapping, (PendingConstructorCreation) rowValue);
1✔
1296
            foundValues = true;
1✔
1297
          }
1298
        } else {
1299
          pendingConstructorCreation.linkCollectionValue(constructorMapping, rowValue);
1✔
1300
          foundValues = true;
1✔
1301

1302
          if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
1303
            nestedResultObjects.put(combinedKey, pendingConstructorCreation);
1✔
1304
          }
1305
        }
1306
      } catch (SQLException e) {
×
1307
        throw new ExecutorException("Error getting constructor collection nested result map values for '"
×
1308
            + constructorMapping.getProperty() + "'.  Cause: " + e, e);
×
1309
      }
1✔
1310
    }
1✔
1311

1312
    return foundValues;
1✔
1313
  }
1314

1315
  private void createPendingConstructorCreations(Object rowValue) {
1316
    // handle possible pending creations within this object
1317
    // by now, the property mapping has been completely built, we can reconstruct it
1318
    final PendingRelation pendingRelation = pendingPccRelations.remove(rowValue);
1✔
1319
    final MetaObject metaObject = pendingRelation.metaObject;
1✔
1320
    final ResultMapping resultMapping = pendingRelation.propertyMapping;
1✔
1321

1322
    // get the list to be built
1323
    Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1✔
1324
    if (collectionProperty != null) {
1!
1325
      // we expect pending creations now
1326
      final Collection<Object> pendingCreations = (Collection<Object>) collectionProperty;
1✔
1327

1328
      // remove the link to the old collection
1329
      metaObject.setValue(resultMapping.getProperty(), null);
1✔
1330

1331
      // create new collection property
1332
      collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1✔
1333
      final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1✔
1334

1335
      // create the pending objects
1336
      for (Object pendingCreation : pendingCreations) {
1✔
1337
        if (pendingCreation instanceof PendingConstructorCreation) {
1!
1338
          final PendingConstructorCreation pendingConstructorCreation = (PendingConstructorCreation) pendingCreation;
1✔
1339
          targetMetaObject.add(pendingConstructorCreation.create(objectFactory));
1✔
1340
        }
1341
      }
1✔
1342
    }
1343
  }
1✔
1344

1345
  private void verifyPendingCreationPreconditions(ResultMapping parentMapping) {
1346
    if (parentMapping != null) {
1!
1347
      throw new ExecutorException(
×
1348
          "Cannot construct objects with collections in constructors using multiple result sets yet!");
1349
    }
1350

1351
    if (!mappedStatement.isResultOrdered()) {
1!
1352
      throw new ExecutorException("Cannot reliably construct result if we are not sure the results are ordered "
×
1353
          + "so that no new previous rows would occur, set resultOrdered on your mapped statement if you have verified this");
1354
    }
1355
  }
1✔
1356

1357
  private void createAndStorePendingCreation(ResultHandler<?> resultHandler, ResultSet resultSet,
1358
      DefaultResultContext<Object> resultContext, PendingConstructorCreation pendingCreation) throws SQLException {
1359
    final Object result = pendingCreation.create(objectFactory);
1✔
1360
    storeObject(resultHandler, resultContext, result, null, resultSet);
1✔
1361
    nestedResultObjects.clear();
1✔
1362
  }
1✔
1363

1364
  //
1365
  // NESTED RESULT MAP (JOIN MAPPING)
1366
  //
1367

1368
  private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
1369
      String parentPrefix, CacheKey parentRowKey, boolean newObject) {
1370
    boolean foundValues = false;
1✔
1371
    for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
1✔
1372
      final String nestedResultMapId = resultMapping.getNestedResultMapId();
1✔
1373
      if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
1!
1374
        try {
1375
          final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
1✔
1376
          final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
1✔
1377
          if (resultMapping.getColumnPrefix() == null) {
1✔
1378
            // try to fill circular reference only when columnPrefix
1379
            // is not specified for the nested result map (issue #215)
1380
            Object ancestorObject = ancestorObjects.get(nestedResultMapId);
1✔
1381
            if (ancestorObject != null) {
1✔
1382
              if (newObject) {
1✔
1383
                linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
1✔
1384
              }
1385
              continue;
1✔
1386
            }
1387
          }
1388
          final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
1✔
1389
          final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
1390
          Object rowValue = nestedResultObjects.get(combinedKey);
1✔
1391
          boolean knownValue = rowValue != null;
1✔
1392
          instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
1✔
1393
          if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
1✔
1394
            rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
1✔
1395
            if (rowValue != null && !knownValue) {
1✔
1396
              linkObjects(metaObject, resultMapping, rowValue);
1✔
1397
              foundValues = true;
1✔
1398
            }
1399
          }
1400
        } catch (SQLException e) {
×
1401
          throw new ExecutorException(
×
1402
              "Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
×
1403
        }
1✔
1404
      }
1405
    }
1✔
1406

1407
    // (issue #101)
1408
    if (resultMap.hasResultMapsUsingConstructorCollection()) {
1✔
1409
      foundValues = applyNestedPendingConstructorCreations(rsw, resultMap, metaObject, parentPrefix, parentRowKey,
1✔
1410
          newObject, foundValues);
1411
    }
1412

1413
    return foundValues;
1✔
1414
  }
1415

1416
  private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) {
1417
    final StringBuilder columnPrefixBuilder = new StringBuilder();
1✔
1418
    if (parentPrefix != null) {
1✔
1419
      columnPrefixBuilder.append(parentPrefix);
1✔
1420
    }
1421
    if (resultMapping.getColumnPrefix() != null) {
1✔
1422
      columnPrefixBuilder.append(resultMapping.getColumnPrefix());
1✔
1423
    }
1424
    return columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH);
1✔
1425
  }
1426

1427
  private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSetWrapper rsw)
1428
      throws SQLException {
1429
    Set<String> notNullColumns = resultMapping.getNotNullColumns();
1✔
1430
    if (notNullColumns != null && !notNullColumns.isEmpty()) {
1✔
1431
      ResultSet rs = rsw.getResultSet();
1✔
1432
      for (String column : notNullColumns) {
1✔
1433
        rs.getObject(prependPrefix(column, columnPrefix));
1✔
1434
        if (!rs.wasNull()) {
1✔
1435
          return true;
1✔
1436
        }
1437
      }
1✔
1438
      return false;
1✔
1439
    }
1440
    if (columnPrefix != null) {
1✔
1441
      for (String columnName : rsw.getColumnNames()) {
1✔
1442
        if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix.toUpperCase(Locale.ENGLISH))) {
1✔
1443
          return true;
1✔
1444
        }
1445
      }
1✔
1446
      return false;
1✔
1447
    }
1448
    return true;
1✔
1449
  }
1450

1451
  private ResultMap getNestedResultMap(ResultSet rs, String nestedResultMapId, String columnPrefix)
1452
      throws SQLException {
1453
    ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId);
1✔
1454
    return resolveDiscriminatedResultMap(rs, nestedResultMap, columnPrefix);
1✔
1455
  }
1456

1457
  //
1458
  // UNIQUE RESULT KEY
1459
  //
1460

1461
  private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
1462
    final CacheKey cacheKey = new CacheKey();
1✔
1463
    cacheKey.update(resultMap.getId());
1✔
1464
    List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
1✔
1465
    if (resultMappings.isEmpty()) {
1✔
1466
      if (Map.class.isAssignableFrom(resultMap.getType())) {
1!
1467
        createRowKeyForMap(rsw, cacheKey);
×
1468
      } else {
1469
        createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
1✔
1470
      }
1471
    } else {
1472
      createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
1✔
1473
    }
1474
    if (cacheKey.getUpdateCount() < 2) {
1✔
1475
      return CacheKey.NULL_CACHE_KEY;
1✔
1476
    }
1477
    return cacheKey;
1✔
1478
  }
1479

1480
  private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) {
1481
    if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) {
1✔
1482
      CacheKey combinedKey;
1483
      try {
1484
        combinedKey = rowKey.clone();
1✔
1485
      } catch (CloneNotSupportedException e) {
×
1486
        throw new ExecutorException("Error cloning cache key.  Cause: " + e, e);
×
1487
      }
1✔
1488
      combinedKey.update(parentRowKey);
1✔
1489
      return combinedKey;
1✔
1490
    }
1491
    return CacheKey.NULL_CACHE_KEY;
1✔
1492
  }
1493

1494
  private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {
1495
    List<ResultMapping> resultMappings = resultMap.getIdResultMappings();
1✔
1496
    if (resultMappings.isEmpty()) {
1✔
1497
      resultMappings = resultMap.getPropertyResultMappings();
1✔
1498
    }
1499
    return resultMappings;
1✔
1500
  }
1501

1502
  private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1503
      List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {
1504
    for (ResultMapping resultMapping : resultMappings) {
1✔
1505
      if (resultMapping.isSimple()) {
1✔
1506
        final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
1✔
1507
        final TypeHandler<?> th = resultMapping.getTypeHandler();
1✔
1508
        Set<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
1✔
1509
        // Issue #114
1510
        if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
1!
1511
          final Object value = th.getResult(rsw.getResultSet(), column);
1✔
1512
          if (value != null || configuration.isReturnInstanceForEmptyRow()) {
1✔
1513
            cacheKey.update(column);
1✔
1514
            cacheKey.update(value);
1✔
1515
          }
1516
        }
1517
      }
1518
    }
1✔
1519
  }
1✔
1520

1521
  private void createRowKeyForUnmappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1522
      String columnPrefix) throws SQLException {
1523
    final MetaClass metaType = MetaClass.forClass(resultMap.getType(), reflectorFactory);
1✔
1524
    List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
1✔
1525
    for (String column : unmappedColumnNames) {
1✔
1526
      String property = column;
1✔
1527
      if (columnPrefix != null && !columnPrefix.isEmpty()) {
1!
1528
        // When columnPrefix is specified, ignore columns without the prefix.
1529
        if (!column.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
1530
          continue;
1✔
1531
        }
1532
        property = column.substring(columnPrefix.length());
1✔
1533
      }
1534
      if (metaType.findProperty(property, configuration.isMapUnderscoreToCamelCase()) != null) {
1✔
1535
        String value = rsw.getResultSet().getString(column);
1✔
1536
        if (value != null) {
1✔
1537
          cacheKey.update(column);
1✔
1538
          cacheKey.update(value);
1✔
1539
        }
1540
      }
1541
    }
1✔
1542
  }
1✔
1543

1544
  private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException {
1545
    List<String> columnNames = rsw.getColumnNames();
×
1546
    for (String columnName : columnNames) {
×
1547
      final String value = rsw.getResultSet().getString(columnName);
×
1548
      if (value != null) {
×
1549
        cacheKey.update(columnName);
×
1550
        cacheKey.update(value);
×
1551
      }
1552
    }
×
1553
  }
×
1554

1555
  private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
1556
    linkObjects(metaObject, resultMapping, rowValue, false);
1✔
1557
  }
1✔
1558

1559
  private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue,
1560
      boolean isNestedCursorResult) {
1561
    final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1✔
1562
    if (collectionProperty != null) {
1✔
1563
      final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1✔
1564
      if (isNestedCursorResult) {
1!
NEW
1565
        targetMetaObject.addAll((List<?>) rowValue);
×
1566
      } else {
1567
        targetMetaObject.add(rowValue);
1✔
1568
      }
1569

1570
      // it is possible for pending creations to get set via property mappings,
1571
      // keep track of these, so we can rebuild them.
1572
      final Object originalObject = metaObject.getOriginalObject();
1✔
1573
      if (rowValue instanceof PendingConstructorCreation && !pendingPccRelations.containsKey(originalObject)) {
1✔
1574
        PendingRelation pendingRelation = new PendingRelation();
1✔
1575
        pendingRelation.propertyMapping = resultMapping;
1✔
1576
        pendingRelation.metaObject = metaObject;
1✔
1577

1578
        pendingPccRelations.put(originalObject, pendingRelation);
1✔
1579
      }
1580
    } else {
1✔
1581
      metaObject.setValue(resultMapping.getProperty(),
1✔
1582
          isNestedCursorResult ? toSingleObj((List<?>) rowValue) : rowValue);
1!
1583
    }
1584
  }
1✔
1585

1586
  private Object toSingleObj(List<?> list) {
1587
    // Even if there are multiple elements, silently returns the first one.
NEW
1588
    return list.isEmpty() ? null : list.get(0);
×
1589
  }
1590

1591
  private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
1592
    final String propertyName = resultMapping.getProperty();
1✔
1593
    Object propertyValue = metaObject.getValue(propertyName);
1✔
1594
    if (propertyValue == null) {
1✔
1595
      Class<?> type = resultMapping.getJavaType();
1✔
1596
      if (type == null) {
1✔
1597
        type = metaObject.getSetterType(propertyName);
1✔
1598
      }
1599
      try {
1600
        if (objectFactory.isCollection(type)) {
1✔
1601
          propertyValue = objectFactory.create(type);
1✔
1602
          metaObject.setValue(propertyName, propertyValue);
1✔
1603
          return propertyValue;
1✔
1604
        }
1605
      } catch (Exception e) {
×
1606
        throw new ExecutorException(
×
1607
            "Error instantiating collection property for result '" + resultMapping.getProperty() + "'.  Cause: " + e,
×
1608
            e);
1609
      }
1✔
1610
    } else if (objectFactory.isCollection(propertyValue.getClass())) {
1✔
1611
      return propertyValue;
1✔
1612
    }
1613
    return null;
1✔
1614
  }
1615

1616
  private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
1617
    if (rsw.getColumnNames().size() == 1) {
1✔
1618
      return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0)));
1✔
1619
    }
1620
    return typeHandlerRegistry.hasTypeHandler(resultType);
1✔
1621
  }
1622

1623
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc