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

mybatis / mybatis-3 / 2682

29 Jan 2025 07:08PM UTC coverage: 87.101% (-0.1%) from 87.217%
2682

Pull #3379

github

web-flow
Merge 1c658c09b into e20272705
Pull Request #3379: Resolve type handler based on `java.lang.reflect.Type` instead of `Class` and respect runtime JDBC type

3812 of 4645 branches covered (82.07%)

475 of 534 new or added lines in 37 files covered. (88.95%)

26 existing lines in 5 files now uncovered.

9906 of 11373 relevant lines covered (87.1%)

0.87 hits per line

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

91.07
/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.lang.reflect.Type;
21
import java.sql.CallableStatement;
22
import java.sql.ResultSet;
23
import java.sql.SQLException;
24
import java.sql.Statement;
25
import java.text.MessageFormat;
26
import java.util.ArrayList;
27
import java.util.Arrays;
28
import java.util.Collection;
29
import java.util.HashMap;
30
import java.util.HashSet;
31
import java.util.IdentityHashMap;
32
import java.util.List;
33
import java.util.Locale;
34
import java.util.Map;
35
import java.util.Optional;
36
import java.util.Set;
37

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

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

83
  private static final Object DEFERRED = new Object();
1✔
84

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

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

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

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

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

112
  private final Map<CacheKey, TypeHandler<?>> typeHandlerCache = new HashMap<>();
1✔
113

114
  // temporary marking flag that indicate using constructor mapping (use field to reduce memory usage)
115
  private boolean useConstructorMappings;
116

117
  private static class PendingRelation {
118
    public MetaObject metaObject;
119
    public ResultMapping propertyMapping;
120
  }
121

122
  private static class UnMappedColumnAutoMapping {
123
    private final String column;
124
    private final String property;
125
    private final TypeHandler<?> typeHandler;
126
    private final boolean primitive;
127

128
    public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
1✔
129
      this.column = column;
1✔
130
      this.property = property;
1✔
131
      this.typeHandler = typeHandler;
1✔
132
      this.primitive = primitive;
1✔
133
    }
1✔
134
  }
135

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

150
  //
151
  // HANDLE OUTPUT PARAMETER
152
  //
153

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

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

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

201
    final List<Object> multipleResults = new ArrayList<>();
1✔
202

203
    int resultSetCount = 0;
1✔
204
    ResultSetWrapper rsw = getFirstResultSet(stmt);
1✔
205

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

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

232
    return collapseSingleResultList(multipleResults);
1✔
233
  }
234

235
  @Override
236
  public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException {
237
    ErrorContext.instance().activity("handling cursor results").object(mappedStatement.getId());
1✔
238

239
    ResultSetWrapper rsw = getFirstResultSet(stmt);
1✔
240

241
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
1✔
242

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

249
    ResultMap resultMap = resultMaps.get(0);
1✔
250
    return new DefaultCursor<>(this, resultMap, rsw, rowBounds);
1✔
251
  }
252

253
  private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
254
    ResultSet rs = null;
1✔
255
    SQLException e1 = null;
1✔
256

257
    try {
258
      rs = stmt.getResultSet();
1✔
259
    } catch (SQLException e) {
×
260
      // Oracle throws ORA-17283 for implicit cursor
261
      e1 = e;
×
262
    }
1✔
263

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

279
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
1✔
280
  }
281

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

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

305
  private void closeResultSet(ResultSet rs) {
306
    try {
307
      if (rs != null) {
1!
308
        rs.close();
1✔
309
      }
310
    } catch (SQLException e) {
×
311
      // ignore
312
    }
1✔
313
  }
1✔
314

315
  private void cleanUpAfterHandlingResultSet() {
316
    nestedResultObjects.clear();
1✔
317
  }
1✔
318

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

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

345
  @SuppressWarnings("unchecked")
346
  private List<Object> collapseSingleResultList(List<Object> multipleResults) {
347
    return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
1✔
348
  }
349

350
  //
351
  // HANDLE ROWS FOR SIMPLE RESULTMAP
352
  //
353

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

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

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

383
  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
384
      ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
385
    final boolean useCollectionConstructorInjection = resultMap.hasResultMapsUsingConstructorCollection();
1✔
386

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

400
        createAndStorePendingCreation(resultHandler, resultSet, resultContext, (PendingConstructorCreation) rowValue);
×
401
      }
402
    }
1✔
403
  }
1✔
404

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

412
    if (pendingPccRelations.containsKey(rowValue)) {
1✔
413
      createPendingConstructorCreations(rowValue);
1✔
414
    }
415

416
    callResultHandler(resultHandler, resultContext, rowValue);
1✔
417
  }
1✔
418

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

426
  private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
427
    return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
1✔
428
  }
429

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

444
  //
445
  // GET VALUE FROM ROW FOR SIMPLE RESULT MAP
446
  //
447

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

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

468
      if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
469
        nestedResultObjects.put(combinedKey, rowValue);
1✔
470
      }
471
    }
472

473
    return rowValue;
1✔
474
  }
475

476
  //
477
  // GET VALUE FROM ROW FOR NESTED RESULT MAP
478
  //
479

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

513
  private void putAncestor(Object resultObject, String resultMapId) {
514
    ancestorObjects.put(resultMapId, resultObject);
1✔
515
  }
1✔
516

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

528
  //
529
  // PROPERTY MAPPINGS
530
  //
531

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

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

594
  private List<Object> getNestedCursorValue(ResultSetWrapper rsw, ResultMapping propertyMapping,
595
      String parentColumnPrefix) throws SQLException {
596
    final String column = prependPrefix(propertyMapping.getColumn(), parentColumnPrefix);
×
NEW
597
    ResultMap nestedResultMap = resolveDiscriminatedResultMap(rsw,
×
598
        configuration.getResultMap(propertyMapping.getNestedResultMapId()),
×
599
        getColumnPrefix(parentColumnPrefix, propertyMapping));
×
NEW
600
    ResultSetWrapper nestedRsw = new ResultSetWrapper(rsw.getResultSet().getObject(column, ResultSet.class),
×
601
        configuration);
602
    List<Object> results = new ArrayList<>();
×
NEW
603
    handleResultSet(nestedRsw, nestedResultMap, results, null);
×
604
    return results;
×
605
  }
606

607
  private TypeHandler<?> resolvePropertyTypeHandler(ResultSetWrapper rsw, MetaObject metaResultObject,
608
      final String property, final String column) {
609
    CacheKey typeHandlerCacheKey = new CacheKey();
1✔
610
    Class<?> metaResultObjectClass = metaResultObject.getOriginalObject().getClass();
1✔
611
    typeHandlerCacheKey.update(metaResultObjectClass);
1✔
612
    typeHandlerCacheKey.update(column);
1✔
613
    typeHandlerCacheKey.update(property);
1✔
614
    return typeHandlerCache.computeIfAbsent(typeHandlerCacheKey, k -> {
1✔
615
      final JdbcType jdbcType = rsw.getJdbcType(column);
1✔
616
      final TypeHandler<?> th;
617
      if (property == null) {
1✔
618
        th = typeHandlerRegistry.getTypeHandler(jdbcType);
1✔
619
      } else {
620
        Type classToHandle = metaResultObject.getGenericSetterType(property).getKey();
1✔
621
        th = configuration.getTypeHandlerResolver().resolve(metaResultObjectClass, classToHandle, property, jdbcType,
1✔
622
            null);
623
        if (th == null) {
1✔
624
          throw new TypeException(
1✔
625
              "No usable type handler found for mapping the result of column '" + column + "' to property '" + property
626
                  + "'. It was either not specified and/or could not be found for the javaType (" + classToHandle
627
                  + ") : jdbcType (" + jdbcType + ") combination.");
628
        }
629
      }
630
      return th;
1✔
631
    });
632
  }
633

634
  private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap,
635
      MetaObject metaObject, String columnPrefix) throws SQLException {
636
    final String mapKey = resultMap.getId() + ":" + columnPrefix;
1✔
637
    List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
1✔
638
    if (autoMapping == null) {
1✔
639
      autoMapping = new ArrayList<>();
1✔
640
      final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
1✔
641
      // Remove the entry to release the memory
642
      List<String> mappedInConstructorAutoMapping = constructorAutoMappingColumns.remove(mapKey);
1✔
643
      if (mappedInConstructorAutoMapping != null) {
1✔
644
        unmappedColumnNames.removeAll(mappedInConstructorAutoMapping);
1✔
645
      }
646
      for (String columnName : unmappedColumnNames) {
1✔
647
        String propertyName = columnName;
1✔
648
        if (columnPrefix != null && !columnPrefix.isEmpty()) {
1!
649
          // When columnPrefix is specified,
650
          // ignore columns without the prefix.
651
          if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
652
            continue;
1✔
653
          }
654
          propertyName = columnName.substring(columnPrefix.length());
1✔
655
        }
656
        final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
1✔
657
        if (property != null && metaObject.hasSetter(property)) {
1✔
658
          if (resultMap.getMappedProperties().contains(property)) {
1✔
659
            continue;
1✔
660
          }
661
          final Type propertyType = metaObject.getGenericSetterType(property).getKey();
1✔
662
          Class<?> metaObjectClass = metaObject.getOriginalObject().getClass();
1✔
663
          TypeHandler<?> typeHandler = configuration.getTypeHandlerResolver().resolve(metaObjectClass, propertyType,
1✔
664
              property, rsw.getJdbcType(columnName), null);
1✔
665
          if (typeHandler != null) {
1✔
666
            autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler,
1✔
667
                propertyType instanceof Class && ((Class<?>) propertyType).isPrimitive()));
1✔
668
          } else {
669
            configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property,
1✔
670
                propertyType);
671
          }
672
        } else {
1✔
673
          configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName,
1✔
674
              property != null ? property : propertyName, null);
1✔
675
        }
676
      }
1✔
677
      autoMappingsCache.put(mapKey, autoMapping);
1✔
678
    }
679
    return autoMapping;
1✔
680
  }
681

682
  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
683
      String columnPrefix) throws SQLException {
684
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
1✔
685
    boolean foundValues = false;
1✔
686
    if (!autoMapping.isEmpty()) {
1✔
687
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
1✔
688
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
1✔
689
        if (value != null) {
1✔
690
          foundValues = true;
1✔
691
        }
692
        if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) {
1!
693
          // gcode issue #377, call setter on nulls (value is not 'found')
694
          metaObject.setValue(mapping.property, value);
1✔
695
        }
696
      }
1✔
697
    }
698
    return foundValues;
1✔
699
  }
700

701
  // MULTIPLE RESULT SETS
702

703
  private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException {
704
    CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(),
1✔
705
        parentMapping.getForeignColumn());
1✔
706
    List<PendingRelation> parents = pendingRelations.get(parentKey);
1✔
707
    if (parents != null) {
1✔
708
      for (PendingRelation parent : parents) {
1✔
709
        if (parent != null && rowValue != null) {
1!
710
          linkObjects(parent.metaObject, parent.propertyMapping, rowValue);
1✔
711
        }
712
      }
1✔
713
    }
714
  }
1✔
715

716
  private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, ResultMapping parentMapping)
717
      throws SQLException {
718
    CacheKey cacheKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(),
1✔
719
        parentMapping.getColumn());
1✔
720
    PendingRelation deferLoad = new PendingRelation();
1✔
721
    deferLoad.metaObject = metaResultObject;
1✔
722
    deferLoad.propertyMapping = parentMapping;
1✔
723
    List<PendingRelation> relations = pendingRelations.computeIfAbsent(cacheKey, k -> new ArrayList<>());
1✔
724
    // issue #255
725
    relations.add(deferLoad);
1✔
726
    ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet());
1✔
727
    if (previous == null) {
1✔
728
      nextResultMaps.put(parentMapping.getResultSet(), parentMapping);
1✔
729
    } else if (!previous.equals(parentMapping)) {
1!
730
      throw new ExecutorException("Two different properties are mapped to the same resultSet");
×
731
    }
732
  }
1✔
733

734
  private CacheKey createKeyForMultipleResults(ResultSet rs, ResultMapping resultMapping, String names, String columns)
735
      throws SQLException {
736
    CacheKey cacheKey = new CacheKey();
1✔
737
    cacheKey.update(resultMapping);
1✔
738
    if (columns != null && names != null) {
1!
739
      String[] columnsArray = columns.split(",");
1✔
740
      String[] namesArray = names.split(",");
1✔
741
      for (int i = 0; i < columnsArray.length; i++) {
1✔
742
        Object value = rs.getString(columnsArray[i]);
1✔
743
        if (value != null) {
1!
744
          cacheKey.update(namesArray[i]);
1✔
745
          cacheKey.update(value);
1✔
746
        }
747
      }
748
    }
749
    return cacheKey;
1✔
750
  }
751

752
  //
753
  // INSTANTIATION & CONSTRUCTOR MAPPING
754
  //
755

756
  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,
757
      String columnPrefix, CacheKey parentRowKey) throws SQLException {
758
    this.useConstructorMappings = false; // reset previous mapping result
1✔
759
    final List<Class<?>> constructorArgTypes = new ArrayList<>();
1✔
760
    final List<Object> constructorArgs = new ArrayList<>();
1✔
761

762
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix,
1✔
763
        parentRowKey);
764
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
1✔
765
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
1✔
766
      for (ResultMapping propertyMapping : propertyMappings) {
1✔
767
        // issue gcode #109 && issue #149
768
        if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
1✔
769
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration,
1✔
770
              objectFactory, constructorArgTypes, constructorArgs);
771
          break;
1✔
772
        }
773
      }
1✔
774

775
      // (issue #101)
776
      if (resultMap.hasResultMapsUsingConstructorCollection() && resultObject instanceof PendingConstructorCreation) {
1!
777
        linkNestedPendingCreations(rsw, resultMap, columnPrefix, parentRowKey,
1✔
778
            (PendingConstructorCreation) resultObject, constructorArgs);
779
      }
780
    }
781

782
    this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
1✔
783
    return resultObject;
1✔
784
  }
785

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

789
    final Class<?> resultType = resultMap.getType();
1✔
790
    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
1✔
791
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
1✔
792
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
1✔
793
      return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
1✔
794
    }
795
    if (!constructorMappings.isEmpty()) {
1✔
796
      return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs,
1✔
797
          columnPrefix, resultMap.hasResultMapsUsingConstructorCollection(), parentRowKey);
1✔
798
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
1✔
799
      return objectFactory.create(resultType);
1✔
800
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
1!
801
      return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes,
1✔
802
          constructorArgs);
803
    }
804
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
×
805
  }
806

807
  Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType,
808
      List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
809
      String columnPrefix, boolean useCollectionConstructorInjection, CacheKey parentRowKey) {
810
    boolean foundValues = false;
1✔
811

812
    for (ResultMapping constructorMapping : constructorMappings) {
1✔
813
      final Class<?> parameterType = constructorMapping.getJavaType();
1✔
814
      final String column = constructorMapping.getColumn();
1✔
815
      final Object value;
816
      try {
817
        if (constructorMapping.getNestedQueryId() != null) {
1✔
818
          value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
1✔
819
        } else if (JdbcType.CURSOR.equals(constructorMapping.getJdbcType())) {
1!
NEW
820
          List<?> result = (List<?>) getNestedCursorValue(rsw, constructorMapping, columnPrefix).get(0);
×
821
          if (objectFactory.isCollection(parameterType)) {
×
822
            MetaObject collection = configuration.newMetaObject(objectFactory.create(parameterType));
×
823
            collection.addAll((List<?>) result);
×
824
            value = collection.getOriginalObject();
×
825
          } else {
×
826
            value = toSingleObj(result);
×
827
          }
828
        } else if (constructorMapping.getNestedResultMapId() != null) {
1✔
829
          final String constructorColumnPrefix = getColumnPrefix(columnPrefix, constructorMapping);
1✔
830
          final ResultMap resultMap = resolveDiscriminatedResultMap(rsw,
1✔
831
              configuration.getResultMap(constructorMapping.getNestedResultMapId()), constructorColumnPrefix);
1✔
832
          value = getRowValue(rsw, resultMap, constructorColumnPrefix,
1✔
833
              useCollectionConstructorInjection ? parentRowKey : null);
1✔
834
        } else {
1✔
835
          TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
1✔
836
          if (typeHandler == null) {
1✔
837
            typeHandler = typeHandlerRegistry.getTypeHandler(constructorMapping.getJavaType(), rsw.getJdbcType(column));
1✔
838
          }
839
          value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
1✔
840
        }
841
      } catch (ResultMapException | SQLException e) {
1✔
842
        throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
1✔
843
      }
1✔
844

845
      constructorArgTypes.add(parameterType);
1✔
846
      constructorArgs.add(value);
1✔
847

848
      foundValues = value != null || foundValues;
1✔
849
    }
1✔
850

851
    if (!foundValues) {
1✔
852
      return null;
1✔
853
    }
854

855
    if (useCollectionConstructorInjection) {
1✔
856
      // at least one of the nestedResultMaps contained a collection, we have to defer until later
857
      return new PendingConstructorCreation(resultType, constructorArgTypes, constructorArgs);
1✔
858
    }
859

860
    return objectFactory.create(resultType, constructorArgTypes, constructorArgs);
1✔
861
  }
862

863
  private Object createByConstructorSignature(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
864
      Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
865
    return applyConstructorAutomapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs,
1✔
866
        findConstructorForAutomapping(resultType, rsw).orElseThrow(() -> new ExecutorException(
1✔
867
            "No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames())));
×
868
  }
869

870
  private Optional<Constructor<?>> findConstructorForAutomapping(final Class<?> resultType, ResultSetWrapper rsw) {
871
    Constructor<?>[] constructors = resultType.getDeclaredConstructors();
1✔
872
    if (constructors.length == 1) {
1✔
873
      return Optional.of(constructors[0]);
1✔
874
    }
875
    Optional<Constructor<?>> annotated = Arrays.stream(constructors)
1✔
876
        .filter(x -> x.isAnnotationPresent(AutomapConstructor.class)).reduce((x, y) -> {
1✔
877
          throw new ExecutorException("@AutomapConstructor should be used in only one constructor.");
1✔
878
        });
879
    if (annotated.isPresent()) {
1✔
880
      return annotated;
1✔
881
    }
882
    if (configuration.isArgNameBasedConstructorAutoMapping()) {
1!
883
      // Finding-best-match type implementation is possible,
884
      // but using @AutomapConstructor seems sufficient.
885
      throw new ExecutorException(MessageFormat.format(
×
886
          "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.",
887
          resultType.getName()));
×
888
    } else {
889
      return Arrays.stream(constructors).filter(x -> findUsableConstructorByArgTypes(x, rsw.getJdbcTypes())).findAny();
1✔
890
    }
891
  }
892

893
  private boolean findUsableConstructorByArgTypes(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) {
894
    final Class<?>[] parameterTypes = constructor.getParameterTypes();
1✔
895
    if (parameterTypes.length != jdbcTypes.size()) {
1✔
896
      return false;
1✔
897
    }
898
    for (int i = 0; i < parameterTypes.length; i++) {
1✔
899
      if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) {
1!
900
        return false;
×
901
      }
902
    }
903
    return true;
1✔
904
  }
905

906
  private Object applyConstructorAutomapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
907
      Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor)
908
      throws SQLException {
909
    boolean foundValues = false;
1✔
910
    if (configuration.isArgNameBasedConstructorAutoMapping()) {
1✔
911
      foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, constructorArgTypes,
1✔
912
          constructorArgs, constructor, foundValues);
913
    } else {
914
      foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor,
1✔
915
          foundValues);
916
    }
917
    return foundValues || configuration.isReturnInstanceForEmptyRow()
1!
918
        ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
1✔
919
  }
920

921
  private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,
922
      List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {
923
    Class<?>[] parameterTypes = constructor.getParameterTypes();
1✔
924

925
    if (parameterTypes.length > rsw.getClassNames().size()) {
1✔
926
      throw new ExecutorException(MessageFormat.format(
1✔
927
          "Constructor auto-mapping of ''{0}'' failed. The constructor takes ''{1}'' arguments, but there are only ''{2}'' columns in the result set.",
928
          constructor, parameterTypes.length, rsw.getClassNames().size()));
1✔
929
    }
930

931
    for (int i = 0; i < parameterTypes.length; i++) {
1✔
932
      Class<?> parameterType = parameterTypes[i];
1✔
933
      String columnName = rsw.getColumnNames().get(i);
1✔
934
      TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
1✔
935
      Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
936
      constructorArgTypes.add(parameterType);
1✔
937
      constructorArgs.add(value);
1✔
938
      foundValues = value != null || foundValues;
1!
939
    }
940
    return foundValues;
1✔
941
  }
942

943
  private boolean applyArgNameBasedConstructorAutoMapping(ResultSetWrapper rsw, ResultMap resultMap,
944
      String columnPrefix, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor,
945
      boolean foundValues) throws SQLException {
946
    List<String> missingArgs = null;
1✔
947
    Parameter[] params = constructor.getParameters();
1✔
948
    for (Parameter param : params) {
1✔
949
      boolean columnNotFound = true;
1✔
950
      Param paramAnno = param.getAnnotation(Param.class);
1✔
951
      String paramName = paramAnno == null ? param.getName() : paramAnno.value();
1✔
952
      for (String columnName : rsw.getColumnNames()) {
1✔
953
        if (columnMatchesParam(columnName, paramName, columnPrefix)) {
1✔
954
          Class<?> paramType = param.getType();
1✔
955
          TypeHandler<?> typeHandler = rsw.getTypeHandler(paramType, columnName);
1✔
956
          Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
957
          constructorArgTypes.add(paramType);
1✔
958
          constructorArgs.add(value);
1✔
959
          final String mapKey = resultMap.getId() + ":" + columnPrefix;
1✔
960
          if (!autoMappingsCache.containsKey(mapKey)) {
1!
961
            constructorAutoMappingColumns.computeIfAbsent(mapKey, k -> new ArrayList<>()).add(columnName);
1✔
962
          }
963
          columnNotFound = false;
1✔
964
          foundValues = value != null || foundValues;
1!
965
        }
966
      }
1✔
967
      if (columnNotFound) {
1✔
968
        if (missingArgs == null) {
1!
969
          missingArgs = new ArrayList<>();
1✔
970
        }
971
        missingArgs.add(paramName);
1✔
972
      }
973
    }
974
    if (foundValues && constructorArgs.size() < params.length) {
1✔
975
      throw new ExecutorException(MessageFormat.format(
1✔
976
          "Constructor auto-mapping of ''{1}'' failed " + "because ''{0}'' were not found in the result set; "
977
              + "Available columns are ''{2}'' and mapUnderscoreToCamelCase is ''{3}''.",
978
          missingArgs, constructor, rsw.getColumnNames(), configuration.isMapUnderscoreToCamelCase()));
1✔
979
    }
980
    return foundValues;
1✔
981
  }
982

983
  private boolean columnMatchesParam(String columnName, String paramName, String columnPrefix) {
984
    if (columnPrefix != null) {
1✔
985
      if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
986
        return false;
1✔
987
      }
988
      columnName = columnName.substring(columnPrefix.length());
1✔
989
    }
990
    return paramName
1✔
991
        .equalsIgnoreCase(configuration.isMapUnderscoreToCamelCase() ? columnName.replace("_", "") : columnName);
1✔
992
  }
993

994
  private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
995
      throws SQLException {
996
    final Class<?> resultType = resultMap.getType();
1✔
997
    final String columnName;
998
    if (!resultMap.getResultMappings().isEmpty()) {
1✔
999
      final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
1✔
1000
      final ResultMapping mapping = resultMappingList.get(0);
1✔
1001
      columnName = prependPrefix(mapping.getColumn(), columnPrefix);
1✔
1002
    } else {
1✔
1003
      columnName = rsw.getColumnNames().get(0);
1✔
1004
    }
1005
    final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
1✔
1006
    return typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
1007
  }
1008

1009
  //
1010
  // NESTED QUERY
1011
  //
1012

1013
  private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)
1014
      throws SQLException {
1015
    final String nestedQueryId = constructorMapping.getNestedQueryId();
1✔
1016
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
1✔
1017
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
1✔
1018
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping,
1✔
1019
        nestedQueryParameterType, columnPrefix);
1020
    Object value = null;
1✔
1021
    if (nestedQueryParameterObject != null) {
1!
1022
      final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
1✔
1023
      final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
1✔
1024
          nestedBoundSql);
1025
      final Class<?> targetType = constructorMapping.getJavaType();
1✔
1026
      final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
1✔
1027
          nestedQueryParameterObject, targetType, key, nestedBoundSql);
1028
      value = resultLoader.loadResult();
1✔
1029
    }
1030
    return value;
1✔
1031
  }
1032

1033
  private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
1034
      ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
1035
    final String nestedQueryId = propertyMapping.getNestedQueryId();
1✔
1036
    final String property = propertyMapping.getProperty();
1✔
1037
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
1✔
1038
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
1✔
1039
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping,
1✔
1040
        nestedQueryParameterType, columnPrefix);
1041
    Object value = null;
1✔
1042
    if (nestedQueryParameterObject != null) {
1✔
1043
      final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
1✔
1044
      final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
1✔
1045
          nestedBoundSql);
1046
      final Class<?> targetType = propertyMapping.getJavaType();
1✔
1047
      if (executor.isCached(nestedQuery, key)) {
1✔
1048
        executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
1✔
1049
        value = DEFERRED;
1✔
1050
      } else {
1051
        final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
1✔
1052
            nestedQueryParameterObject, targetType, key, nestedBoundSql);
1053
        if (propertyMapping.isLazy()) {
1✔
1054
          lazyLoader.addLoader(property, metaResultObject, resultLoader);
1✔
1055
          value = DEFERRED;
1✔
1056
        } else {
1057
          value = resultLoader.loadResult();
1✔
1058
        }
1059
      }
1060
    }
1061
    return value;
1✔
1062
  }
1063

1064
  private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
1065
      String columnPrefix) throws SQLException {
1066
    if (resultMapping.isCompositeResult()) {
1✔
1067
      return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix);
1✔
1068
    }
1069
    return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix);
1✔
1070
  }
1071

1072
  private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
1073
      String columnPrefix) throws SQLException {
1074
    final TypeHandler<?> typeHandler;
1075
    if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
1✔
1076
      typeHandler = typeHandlerRegistry.getTypeHandler(parameterType);
1✔
1077
    } else {
1078
      typeHandler = typeHandlerRegistry.getUnknownTypeHandler();
1✔
1079
    }
1080
    return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
1✔
1081
  }
1082

1083
  private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
1084
      String columnPrefix) throws SQLException {
1085
    final Object parameterObject = instantiateParameterObject(parameterType);
1✔
1086
    final MetaObject metaObject = configuration.newMetaObject(parameterObject);
1✔
1087
    boolean foundValues = false;
1✔
1088
    for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
1✔
1089
      final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty());
1✔
1090
      final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType);
1✔
1091
      final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix));
1✔
1092
      // issue #353 & #560 do not execute nested query if key is null
1093
      if (propValue != null) {
1✔
1094
        metaObject.setValue(innerResultMapping.getProperty(), propValue);
1✔
1095
        foundValues = true;
1✔
1096
      }
1097
    }
1✔
1098
    return foundValues ? parameterObject : null;
1✔
1099
  }
1100

1101
  private Object instantiateParameterObject(Class<?> parameterType) {
1102
    if (parameterType == null) {
1✔
1103
      return new HashMap<>();
1✔
1104
    }
1105
    if (ParamMap.class.equals(parameterType)) {
1✔
1106
      return new HashMap<>(); // issue #649
1✔
1107
    } else {
1108
      return objectFactory.create(parameterType);
1✔
1109
    }
1110
  }
1111

1112
  //
1113
  // DISCRIMINATOR
1114
  //
1115

1116
  public ResultMap resolveDiscriminatedResultMap(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
1117
      throws SQLException {
1118
    Set<String> pastDiscriminators = new HashSet<>();
1✔
1119
    Discriminator discriminator = resultMap.getDiscriminator();
1✔
1120
    while (discriminator != null) {
1✔
1121
      final Object value = getDiscriminatorValue(rsw, discriminator, columnPrefix);
1✔
1122
      final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
1✔
1123
      if (!configuration.hasResultMap(discriminatedMapId)) {
1✔
1124
        break;
1✔
1125
      }
1126
      resultMap = configuration.getResultMap(discriminatedMapId);
1✔
1127
      Discriminator lastDiscriminator = discriminator;
1✔
1128
      discriminator = resultMap.getDiscriminator();
1✔
1129
      if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
1!
1130
        break;
1✔
1131
      }
1132
    }
1✔
1133
    return resultMap;
1✔
1134
  }
1135

1136
  private Object getDiscriminatorValue(ResultSetWrapper rsw, Discriminator discriminator, String columnPrefix)
1137
      throws SQLException {
1138
    final ResultMapping resultMapping = discriminator.getResultMapping();
1✔
1139
    String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
1✔
1140
    TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
1✔
1141
    if (typeHandler == null) {
1✔
1142
      typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.getJavaType(), rsw.getJdbcType(column));
1✔
1143
    }
1144
    return typeHandler.getResult(rsw.getResultSet(), column);
1✔
1145
  }
1146

1147
  private String prependPrefix(String columnName, String prefix) {
1148
    if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
1!
1149
      return columnName;
1✔
1150
    }
1151
    return prefix + columnName;
1✔
1152
  }
1153

1154
  //
1155
  // HANDLE NESTED RESULT MAPS
1156
  //
1157

1158
  private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap,
1159
      ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
1160
    final boolean useCollectionConstructorInjection = resultMap.hasResultMapsUsingConstructorCollection();
1✔
1161
    PendingConstructorCreation lastHandledCreation = null;
1✔
1162
    if (useCollectionConstructorInjection) {
1✔
1163
      verifyPendingCreationPreconditions(parentMapping);
1✔
1164
    }
1165

1166
    final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
1✔
1167
    ResultSet resultSet = rsw.getResultSet();
1✔
1168
    skipRows(resultSet, rowBounds);
1✔
1169
    Object rowValue = previousRowValue;
1✔
1170

1171
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
1!
1172
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw, resultMap, null);
1✔
1173
      final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
1✔
1174

1175
      final Object partialObject = nestedResultObjects.get(rowKey);
1✔
1176
      final boolean foundNewUniqueRow = partialObject == null;
1✔
1177

1178
      // issue #577, #542 && #101
1179
      if (useCollectionConstructorInjection) {
1✔
1180
        if (foundNewUniqueRow && lastHandledCreation != null) {
1✔
1181
          createAndStorePendingCreation(resultHandler, resultSet, resultContext, lastHandledCreation);
1✔
1182
          lastHandledCreation = null;
1✔
1183
        }
1184

1185
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1186
        if (rowValue instanceof PendingConstructorCreation) {
1!
1187
          lastHandledCreation = (PendingConstructorCreation) rowValue;
1✔
1188
        }
1189
      } else if (mappedStatement.isResultOrdered()) {
1✔
1190
        if (foundNewUniqueRow && rowValue != null) {
1✔
1191
          nestedResultObjects.clear();
1✔
1192
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1193
        }
1194
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1195
      } else {
1196
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1197
        if (foundNewUniqueRow) {
1✔
1198
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1199
        }
1200
      }
1201
    }
1✔
1202

1203
    if (useCollectionConstructorInjection && lastHandledCreation != null) {
1!
1204
      createAndStorePendingCreation(resultHandler, resultSet, resultContext, lastHandledCreation);
1✔
1205
    } else if (rowValue != null && mappedStatement.isResultOrdered()
1✔
1206
        && shouldProcessMoreRows(resultContext, rowBounds)) {
1✔
1207
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1208
      previousRowValue = null;
1✔
1209
    } else if (rowValue != null) {
1✔
1210
      previousRowValue = rowValue;
1✔
1211
    }
1212
  }
1✔
1213

1214
  //
1215
  // NESTED RESULT MAP (PENDING CONSTRUCTOR CREATIONS)
1216
  //
1217
  private void linkNestedPendingCreations(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
1218
      CacheKey parentRowKey, PendingConstructorCreation pendingCreation, List<Object> constructorArgs)
1219
      throws SQLException {
1220
    if (parentRowKey == null) {
1!
1221
      // nothing to link, possibly due to simple (non-nested) result map
1222
      return;
×
1223
    }
1224

1225
    final CacheKey rowKey = createRowKey(resultMap, rsw, columnPrefix);
1✔
1226
    final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
1227

1228
    if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1!
1229
      nestedResultObjects.put(combinedKey, pendingCreation);
1✔
1230
    }
1231

1232
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
1✔
1233
    for (int index = 0; index < constructorMappings.size(); index++) {
1✔
1234
      final ResultMapping constructorMapping = constructorMappings.get(index);
1✔
1235
      final String nestedResultMapId = constructorMapping.getNestedResultMapId();
1✔
1236

1237
      if (nestedResultMapId == null) {
1✔
1238
        continue;
1✔
1239
      }
1240

1241
      final Class<?> javaType = constructorMapping.getJavaType();
1✔
1242
      if (javaType == null || !objectFactory.isCollection(javaType)) {
1!
1243
        continue;
1✔
1244
      }
1245

1246
      final String constructorColumnPrefix = getColumnPrefix(columnPrefix, constructorMapping);
1✔
1247
      final ResultMap nestedResultMap = resolveDiscriminatedResultMap(rsw,
1✔
1248
          configuration.getResultMap(constructorMapping.getNestedResultMapId()), constructorColumnPrefix);
1✔
1249

1250
      final Object actualValue = constructorArgs.get(index);
1✔
1251
      final boolean hasValue = actualValue != null;
1✔
1252
      final boolean isInnerCreation = actualValue instanceof PendingConstructorCreation;
1✔
1253
      final boolean alreadyCreatedCollection = hasValue && objectFactory.isCollection(actualValue.getClass());
1!
1254

1255
      if (!isInnerCreation) {
1✔
1256
        final Collection<Object> value = pendingCreation.initializeCollectionForResultMapping(objectFactory,
1✔
1257
            nestedResultMap, constructorMapping, index);
1✔
1258
        if (!alreadyCreatedCollection) {
1!
1259
          // override values with empty collection
1260
          constructorArgs.set(index, value);
1✔
1261
        }
1262

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

1267
        if (nestedCombinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
1268
          nestedResultObjects.put(nestedCombinedKey, pendingCreation);
1✔
1269
        }
1270

1271
        if (hasValue) {
1✔
1272
          pendingCreation.linkCollectionValue(constructorMapping, actualValue);
1✔
1273
        }
1274
      } else {
1✔
1275
        final PendingConstructorCreation innerCreation = (PendingConstructorCreation) actualValue;
1✔
1276
        final Collection<Object> value = pendingCreation.initializeCollectionForResultMapping(objectFactory,
1✔
1277
            nestedResultMap, constructorMapping, index);
1✔
1278
        // we will fill this collection when building the final object
1279
        constructorArgs.set(index, value);
1✔
1280
        // link the creation for building later
1281
        pendingCreation.linkCreation(constructorMapping, innerCreation);
1✔
1282
      }
1283
    }
1284
  }
1✔
1285

1286
  private boolean applyNestedPendingConstructorCreations(ResultSetWrapper rsw, ResultMap resultMap,
1287
      MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject, boolean foundValues) {
1288
    if (newObject) {
1✔
1289
      // new objects are linked by createResultObject
1290
      return false;
1✔
1291
    }
1292

1293
    for (ResultMapping constructorMapping : resultMap.getConstructorResultMappings()) {
1✔
1294
      final String nestedResultMapId = constructorMapping.getNestedResultMapId();
1✔
1295
      final Class<?> parameterType = constructorMapping.getJavaType();
1✔
1296
      if (nestedResultMapId == null || constructorMapping.getResultSet() != null || parameterType == null
1!
1297
          || !objectFactory.isCollection(parameterType)) {
1✔
1298
        continue;
1✔
1299
      }
1300

1301
      try {
1302
        final String columnPrefix = getColumnPrefix(parentPrefix, constructorMapping);
1✔
1303
        final ResultMap nestedResultMap = getNestedResultMap(rsw, nestedResultMapId, columnPrefix);
1✔
1304

1305
        final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
1✔
1306
        final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
1307

1308
        // should have inserted already as a nested result object
1309
        Object rowValue = nestedResultObjects.get(combinedKey);
1✔
1310

1311
        PendingConstructorCreation pendingConstructorCreation = null;
1✔
1312
        if (rowValue instanceof PendingConstructorCreation) {
1✔
1313
          pendingConstructorCreation = (PendingConstructorCreation) rowValue;
1✔
1314
        } else if (rowValue != null) {
1✔
1315
          // found a simple object that was already linked/handled
1316
          continue;
1✔
1317
        }
1318

1319
        final boolean newValueForNestedResultMap = pendingConstructorCreation == null;
1✔
1320
        if (newValueForNestedResultMap) {
1✔
1321
          final Object parentObject = metaObject.getOriginalObject();
1✔
1322
          if (!(parentObject instanceof PendingConstructorCreation)) {
1!
1323
            throw new ExecutorException(
×
1324
                "parentObject is not a pending creation, cannot continue linking! MyBatis internal error!");
1325
          }
1326

1327
          pendingConstructorCreation = (PendingConstructorCreation) parentObject;
1✔
1328
        }
1329

1330
        rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix,
1✔
1331
            newValueForNestedResultMap ? null : pendingConstructorCreation);
1✔
1332

1333
        if (rowValue == null) {
1✔
1334
          continue;
1✔
1335
        }
1336

1337
        if (rowValue instanceof PendingConstructorCreation) {
1✔
1338
          if (newValueForNestedResultMap) {
1✔
1339
            // we created a brand new pcc. this is a new collection value
1340
            pendingConstructorCreation.linkCreation(constructorMapping, (PendingConstructorCreation) rowValue);
1✔
1341
            foundValues = true;
1✔
1342
          }
1343
        } else {
1344
          pendingConstructorCreation.linkCollectionValue(constructorMapping, rowValue);
1✔
1345
          foundValues = true;
1✔
1346

1347
          if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
1348
            nestedResultObjects.put(combinedKey, pendingConstructorCreation);
1✔
1349
          }
1350
        }
1351
      } catch (SQLException e) {
×
1352
        throw new ExecutorException("Error getting constructor collection nested result map values for '"
×
1353
            + constructorMapping.getProperty() + "'.  Cause: " + e, e);
×
1354
      }
1✔
1355
    }
1✔
1356

1357
    return foundValues;
1✔
1358
  }
1359

1360
  private void createPendingConstructorCreations(Object rowValue) {
1361
    // handle possible pending creations within this object
1362
    // by now, the property mapping has been completely built, we can reconstruct it
1363
    final PendingRelation pendingRelation = pendingPccRelations.remove(rowValue);
1✔
1364
    final MetaObject metaObject = pendingRelation.metaObject;
1✔
1365
    final ResultMapping resultMapping = pendingRelation.propertyMapping;
1✔
1366

1367
    // get the list to be built
1368
    Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1✔
1369
    if (collectionProperty != null) {
1!
1370
      // we expect pending creations now
1371
      final Collection<Object> pendingCreations = (Collection<Object>) collectionProperty;
1✔
1372

1373
      // remove the link to the old collection
1374
      metaObject.setValue(resultMapping.getProperty(), null);
1✔
1375

1376
      // create new collection property
1377
      collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1✔
1378
      final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1✔
1379

1380
      // create the pending objects
1381
      for (Object pendingCreation : pendingCreations) {
1✔
1382
        if (pendingCreation instanceof PendingConstructorCreation) {
1!
1383
          final PendingConstructorCreation pendingConstructorCreation = (PendingConstructorCreation) pendingCreation;
1✔
1384
          targetMetaObject.add(pendingConstructorCreation.create(objectFactory));
1✔
1385
        }
1386
      }
1✔
1387
    }
1388
  }
1✔
1389

1390
  private void verifyPendingCreationPreconditions(ResultMapping parentMapping) {
1391
    if (parentMapping != null) {
1!
1392
      throw new ExecutorException(
×
1393
          "Cannot construct objects with collections in constructors using multiple result sets yet!");
1394
    }
1395

1396
    if (!mappedStatement.isResultOrdered()) {
1!
1397
      throw new ExecutorException("Cannot reliably construct result if we are not sure the results are ordered "
×
1398
          + "so that no new previous rows would occur, set resultOrdered on your mapped statement if you have verified this");
1399
    }
1400
  }
1✔
1401

1402
  private void createAndStorePendingCreation(ResultHandler<?> resultHandler, ResultSet resultSet,
1403
      DefaultResultContext<Object> resultContext, PendingConstructorCreation pendingCreation) throws SQLException {
1404
    final Object result = pendingCreation.create(objectFactory);
1✔
1405
    storeObject(resultHandler, resultContext, result, null, resultSet);
1✔
1406
    nestedResultObjects.clear();
1✔
1407
  }
1✔
1408

1409
  //
1410
  // NESTED RESULT MAP (JOIN MAPPING)
1411
  //
1412

1413
  private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
1414
      String parentPrefix, CacheKey parentRowKey, boolean newObject) {
1415
    boolean foundValues = false;
1✔
1416
    for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
1✔
1417
      final String nestedResultMapId = resultMapping.getNestedResultMapId();
1✔
1418
      if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
1!
1419
        try {
1420
          final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
1✔
1421
          final ResultMap nestedResultMap = getNestedResultMap(rsw, nestedResultMapId, columnPrefix);
1✔
1422
          if (resultMapping.getColumnPrefix() == null) {
1✔
1423
            // try to fill circular reference only when columnPrefix
1424
            // is not specified for the nested result map (issue #215)
1425
            Object ancestorObject = ancestorObjects.get(nestedResultMapId);
1✔
1426
            if (ancestorObject != null) {
1✔
1427
              if (newObject) {
1✔
1428
                linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
1✔
1429
              }
1430
              continue;
1✔
1431
            }
1432
          }
1433
          final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
1✔
1434
          final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
1435
          Object rowValue = nestedResultObjects.get(combinedKey);
1✔
1436
          boolean knownValue = rowValue != null;
1✔
1437
          instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
1✔
1438
          if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
1✔
1439
            rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
1✔
1440
            if (rowValue != null && !knownValue) {
1✔
1441
              linkObjects(metaObject, resultMapping, rowValue);
1✔
1442
              foundValues = true;
1✔
1443
            }
1444
          }
1445
        } catch (SQLException e) {
×
1446
          throw new ExecutorException(
×
1447
              "Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
×
1448
        }
1✔
1449
      }
1450
    }
1✔
1451

1452
    // (issue #101)
1453
    if (resultMap.hasResultMapsUsingConstructorCollection()) {
1✔
1454
      foundValues = applyNestedPendingConstructorCreations(rsw, resultMap, metaObject, parentPrefix, parentRowKey,
1✔
1455
          newObject, foundValues);
1456
    }
1457

1458
    return foundValues;
1✔
1459
  }
1460

1461
  private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) {
1462
    final StringBuilder columnPrefixBuilder = new StringBuilder();
1✔
1463
    if (parentPrefix != null) {
1✔
1464
      columnPrefixBuilder.append(parentPrefix);
1✔
1465
    }
1466
    if (resultMapping.getColumnPrefix() != null) {
1✔
1467
      columnPrefixBuilder.append(resultMapping.getColumnPrefix());
1✔
1468
    }
1469
    return columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH);
1✔
1470
  }
1471

1472
  private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSetWrapper rsw)
1473
      throws SQLException {
1474
    Set<String> notNullColumns = resultMapping.getNotNullColumns();
1✔
1475
    if (notNullColumns != null && !notNullColumns.isEmpty()) {
1✔
1476
      ResultSet rs = rsw.getResultSet();
1✔
1477
      for (String column : notNullColumns) {
1✔
1478
        rs.getObject(prependPrefix(column, columnPrefix));
1✔
1479
        if (!rs.wasNull()) {
1✔
1480
          return true;
1✔
1481
        }
1482
      }
1✔
1483
      return false;
1✔
1484
    }
1485
    if (columnPrefix != null) {
1✔
1486
      for (String columnName : rsw.getColumnNames()) {
1✔
1487
        if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix.toUpperCase(Locale.ENGLISH))) {
1✔
1488
          return true;
1✔
1489
        }
1490
      }
1✔
1491
      return false;
1✔
1492
    }
1493
    return true;
1✔
1494
  }
1495

1496
  private ResultMap getNestedResultMap(ResultSetWrapper rsw, String nestedResultMapId, String columnPrefix)
1497
      throws SQLException {
1498
    ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId);
1✔
1499
    return resolveDiscriminatedResultMap(rsw, nestedResultMap, columnPrefix);
1✔
1500
  }
1501

1502
  //
1503
  // UNIQUE RESULT KEY
1504
  //
1505

1506
  private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
1507
    final CacheKey cacheKey = new CacheKey();
1✔
1508
    cacheKey.update(resultMap.getId());
1✔
1509
    List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
1✔
1510
    if (resultMappings.isEmpty()) {
1✔
1511
      if (Map.class.isAssignableFrom(resultMap.getType())) {
1!
1512
        createRowKeyForMap(rsw, cacheKey);
×
1513
      } else {
1514
        createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
1✔
1515
      }
1516
    } else {
1517
      createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
1✔
1518
    }
1519
    if (cacheKey.getUpdateCount() < 2) {
1✔
1520
      return CacheKey.NULL_CACHE_KEY;
1✔
1521
    }
1522
    return cacheKey;
1✔
1523
  }
1524

1525
  private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) {
1526
    if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) {
1✔
1527
      CacheKey combinedKey;
1528
      try {
1529
        combinedKey = rowKey.clone();
1✔
1530
      } catch (CloneNotSupportedException e) {
×
1531
        throw new ExecutorException("Error cloning cache key.  Cause: " + e, e);
×
1532
      }
1✔
1533
      combinedKey.update(parentRowKey);
1✔
1534
      return combinedKey;
1✔
1535
    }
1536
    return CacheKey.NULL_CACHE_KEY;
1✔
1537
  }
1538

1539
  private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {
1540
    List<ResultMapping> resultMappings = resultMap.getIdResultMappings();
1✔
1541
    if (resultMappings.isEmpty()) {
1✔
1542
      resultMappings = resultMap.getPropertyResultMappings();
1✔
1543
    }
1544
    return resultMappings;
1✔
1545
  }
1546

1547
  private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1548
      List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {
1549
    for (ResultMapping resultMapping : resultMappings) {
1✔
1550
      if (resultMapping.isSimple()) {
1✔
1551
        final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
1✔
1552
        TypeHandler<?> th = resultMapping.getTypeHandler();
1✔
1553
        Set<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
1✔
1554
        // Issue #114
1555
        if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
1!
1556
          if (th == null) {
1✔
1557
            th = typeHandlerRegistry.getTypeHandler(rsw.getJdbcType(column));
1✔
1558
          }
1559
          final Object value = th.getResult(rsw.getResultSet(), column);
1✔
1560
          if (value != null || configuration.isReturnInstanceForEmptyRow()) {
1✔
1561
            cacheKey.update(column);
1✔
1562
            cacheKey.update(value);
1✔
1563
          }
1564
        }
1565
      }
1566
    }
1✔
1567
  }
1✔
1568

1569
  private void createRowKeyForUnmappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1570
      String columnPrefix) throws SQLException {
1571
    final MetaClass metaType = MetaClass.forClass(resultMap.getType(), reflectorFactory);
1✔
1572
    List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
1✔
1573
    for (String column : unmappedColumnNames) {
1✔
1574
      String property = column;
1✔
1575
      if (columnPrefix != null && !columnPrefix.isEmpty()) {
1!
1576
        // When columnPrefix is specified, ignore columns without the prefix.
1577
        if (!column.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
1578
          continue;
1✔
1579
        }
1580
        property = column.substring(columnPrefix.length());
1✔
1581
      }
1582
      if (metaType.findProperty(property, configuration.isMapUnderscoreToCamelCase()) != null) {
1✔
1583
        String value = rsw.getResultSet().getString(column);
1✔
1584
        if (value != null) {
1✔
1585
          cacheKey.update(column);
1✔
1586
          cacheKey.update(value);
1✔
1587
        }
1588
      }
1589
    }
1✔
1590
  }
1✔
1591

1592
  private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException {
1593
    List<String> columnNames = rsw.getColumnNames();
×
1594
    for (String columnName : columnNames) {
×
1595
      final String value = rsw.getResultSet().getString(columnName);
×
1596
      if (value != null) {
×
1597
        cacheKey.update(columnName);
×
1598
        cacheKey.update(value);
×
1599
      }
1600
    }
×
1601
  }
×
1602

1603
  private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
1604
    linkObjects(metaObject, resultMapping, rowValue, false);
1✔
1605
  }
1✔
1606

1607
  private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue,
1608
      boolean isNestedCursorResult) {
1609
    final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1✔
1610
    if (collectionProperty != null) {
1✔
1611
      final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1✔
1612
      if (isNestedCursorResult) {
1!
1613
        targetMetaObject.addAll((List<?>) rowValue);
×
1614
      } else {
1615
        targetMetaObject.add(rowValue);
1✔
1616
      }
1617

1618
      // it is possible for pending creations to get set via property mappings,
1619
      // keep track of these, so we can rebuild them.
1620
      final Object originalObject = metaObject.getOriginalObject();
1✔
1621
      if (rowValue instanceof PendingConstructorCreation && !pendingPccRelations.containsKey(originalObject)) {
1✔
1622
        PendingRelation pendingRelation = new PendingRelation();
1✔
1623
        pendingRelation.propertyMapping = resultMapping;
1✔
1624
        pendingRelation.metaObject = metaObject;
1✔
1625

1626
        pendingPccRelations.put(originalObject, pendingRelation);
1✔
1627
      }
1628
    } else {
1✔
1629
      metaObject.setValue(resultMapping.getProperty(),
1✔
1630
          isNestedCursorResult ? toSingleObj((List<?>) rowValue) : rowValue);
1!
1631
    }
1632
  }
1✔
1633

1634
  private Object toSingleObj(List<?> list) {
1635
    // Even if there are multiple elements, silently returns the first one.
1636
    return list.isEmpty() ? null : list.get(0);
×
1637
  }
1638

1639
  private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
1640
    final String propertyName = resultMapping.getProperty();
1✔
1641
    Object propertyValue = metaObject.getValue(propertyName);
1✔
1642
    if (propertyValue == null) {
1✔
1643
      Class<?> type = resultMapping.getJavaType();
1✔
1644
      if (type == null) {
1✔
1645
        type = metaObject.getSetterType(propertyName);
1✔
1646
      }
1647
      try {
1648
        if (objectFactory.isCollection(type)) {
1✔
1649
          propertyValue = objectFactory.create(type);
1✔
1650
          metaObject.setValue(propertyName, propertyValue);
1✔
1651
          return propertyValue;
1✔
1652
        }
1653
      } catch (Exception e) {
×
1654
        throw new ExecutorException(
×
1655
            "Error instantiating collection property for result '" + resultMapping.getProperty() + "'.  Cause: " + e,
×
1656
            e);
1657
      }
1✔
1658
    } else if (objectFactory.isCollection(propertyValue.getClass())) {
1✔
1659
      return propertyValue;
1✔
1660
    }
1661
    return null;
1✔
1662
  }
1663

1664
  private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
1665
    if (rsw.getColumnNames().size() == 1) {
1✔
1666
      return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0)));
1✔
1667
    }
1668
    return typeHandlerRegistry.hasTypeHandler(resultType);
1✔
1669
  }
1670

1671
}
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