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

mybatis / mybatis-3 / 2692

09 Feb 2025 11:57AM UTC coverage: 87.238% (+0.02%) from 87.217%
2692

Pull #3379

github

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

3821 of 4641 branches covered (82.33%)

531 of 597 new or added lines in 40 files covered. (88.94%)

13 existing lines in 4 files now uncovered.

9891 of 11338 relevant lines covered (87.24%)

0.87 hits per line

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

90.85
/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.ResultSetMetaData;
24
import java.sql.SQLException;
25
import java.sql.Statement;
26
import java.text.MessageFormat;
27
import java.util.ArrayList;
28
import java.util.Arrays;
29
import java.util.Collection;
30
import java.util.HashMap;
31
import java.util.HashSet;
32
import java.util.IdentityHashMap;
33
import java.util.List;
34
import java.util.Locale;
35
import java.util.Map;
36
import java.util.Optional;
37
import java.util.Set;
38

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

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

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

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

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

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

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

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

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

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

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

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

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

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

151
  //
152
  // HANDLE OUTPUT PARAMETER
153
  //
154

155
  @Override
156
  public void handleOutputParameters(CallableStatement cs) throws SQLException {
157
    final Object parameterObject = parameterHandler.getParameterObject();
1✔
158
    final MetaObject metaParam = configuration.newMetaObject(parameterObject);
1✔
159
    final List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
1✔
160
    ResultSetMetaData rsmd = null;
1✔
161
    for (int i = 0; i < parameterMappings.size(); i++) {
1✔
162
      final ParameterMapping parameterMapping = parameterMappings.get(i);
1✔
163
      if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) {
1!
164
        if (ResultSet.class.equals(parameterMapping.getJavaType())) {
1!
165
          handleRefCursorOutputParameter((ResultSet) cs.getObject(i + 1), parameterMapping, metaParam);
×
166
        } else {
167
          final String property = parameterMapping.getProperty();
1✔
168
          TypeHandler<?> typeHandler = parameterMapping.getTypeHandler();
1✔
169
          if (typeHandler == null) {
1✔
170
            Class<?> javaType = parameterMapping.getJavaType();
1✔
171
            if (javaType == null || javaType == Object.class) {
1!
172
              metaParam.getGenericSetterType(property);
1✔
173
            }
174
            JdbcType jdbcType = parameterMapping.getJdbcType();
1✔
175
            if (jdbcType == null) {
1!
NEW
176
              if (rsmd == null) {
×
NEW
177
                rsmd = cs.getMetaData();
×
178
              }
NEW
179
              jdbcType = JdbcType.forCode(rsmd.getColumnType(i + 1));
×
180
            }
181
            typeHandler = typeHandlerRegistry.resolve(parameterObject.getClass(), javaType, jdbcType, null);
1✔
182
          }
183
          metaParam.setValue(property, typeHandler.getResult(cs, i + 1));
1✔
184
        }
185
      }
186
    }
187
  }
1✔
188

189
  private void handleRefCursorOutputParameter(ResultSet rs, ParameterMapping parameterMapping, MetaObject metaParam)
190
      throws SQLException {
191
    if (rs == null) {
×
192
      return;
×
193
    }
194
    try {
195
      final String resultMapId = parameterMapping.getResultMapId();
×
196
      final ResultMap resultMap = configuration.getResultMap(resultMapId);
×
197
      final ResultSetWrapper rsw = new ResultSetWrapper(rs, configuration);
×
198
      if (this.resultHandler == null) {
×
199
        final DefaultResultHandler resultHandler = new DefaultResultHandler(objectFactory);
×
200
        handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
×
201
        metaParam.setValue(parameterMapping.getProperty(), resultHandler.getResultList());
×
202
      } else {
×
203
        handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
×
204
      }
205
    } finally {
206
      // issue #228 (close resultsets)
207
      closeResultSet(rs);
×
208
    }
209
  }
×
210

211
  //
212
  // HANDLE RESULT SETS
213
  //
214
  @Override
215
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
216
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
1✔
217

218
    final List<Object> multipleResults = new ArrayList<>();
1✔
219

220
    int resultSetCount = 0;
1✔
221
    ResultSetWrapper rsw = getFirstResultSet(stmt);
1✔
222

223
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
1✔
224
    int resultMapCount = resultMaps.size();
1✔
225
    validateResultMapsCount(rsw, resultMapCount);
1✔
226
    while (rsw != null && resultMapCount > resultSetCount) {
1✔
227
      ResultMap resultMap = resultMaps.get(resultSetCount);
1✔
228
      handleResultSet(rsw, resultMap, multipleResults, null);
1✔
229
      rsw = getNextResultSet(stmt);
1✔
230
      cleanUpAfterHandlingResultSet();
1✔
231
      resultSetCount++;
1✔
232
    }
1✔
233

234
    String[] resultSets = mappedStatement.getResultSets();
1✔
235
    if (resultSets != null) {
1✔
236
      while (rsw != null && resultSetCount < resultSets.length) {
1!
237
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
1✔
238
        if (parentMapping != null) {
1!
239
          String nestedResultMapId = parentMapping.getNestedResultMapId();
1✔
240
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
1✔
241
          handleResultSet(rsw, resultMap, null, parentMapping);
1✔
242
        }
243
        rsw = getNextResultSet(stmt);
1✔
244
        cleanUpAfterHandlingResultSet();
1✔
245
        resultSetCount++;
1✔
246
      }
1✔
247
    }
248

249
    return collapseSingleResultList(multipleResults);
1✔
250
  }
251

252
  @Override
253
  public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException {
254
    ErrorContext.instance().activity("handling cursor results").object(mappedStatement.getId());
1✔
255

256
    ResultSetWrapper rsw = getFirstResultSet(stmt);
1✔
257

258
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
1✔
259

260
    int resultMapCount = resultMaps.size();
1✔
261
    validateResultMapsCount(rsw, resultMapCount);
1✔
262
    if (resultMapCount != 1) {
1!
263
      throw new ExecutorException("Cursor results cannot be mapped to multiple resultMaps");
×
264
    }
265

266
    ResultMap resultMap = resultMaps.get(0);
1✔
267
    return new DefaultCursor<>(this, resultMap, rsw, rowBounds);
1✔
268
  }
269

270
  private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
271
    ResultSet rs = null;
1✔
272
    SQLException e1 = null;
1✔
273

274
    try {
275
      rs = stmt.getResultSet();
1✔
276
    } catch (SQLException e) {
×
277
      // Oracle throws ORA-17283 for implicit cursor
278
      e1 = e;
×
279
    }
1✔
280

281
    try {
282
      while (rs == null) {
1✔
283
        // move forward to get the first resultset in case the driver
284
        // doesn't return the resultset as the first result (HSQLDB)
285
        if (stmt.getMoreResults()) {
1!
286
          rs = stmt.getResultSet();
×
287
        } else if (stmt.getUpdateCount() == -1) {
1!
288
          // no more results. Must be no resultset
289
          break;
1✔
290
        }
291
      }
292
    } catch (SQLException e) {
×
293
      throw e1 != null ? e1 : e;
×
294
    }
1✔
295

296
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
1✔
297
  }
298

299
  private ResultSetWrapper getNextResultSet(Statement stmt) {
300
    // Making this method tolerant of bad JDBC drivers
301
    try {
302
      // We stopped checking DatabaseMetaData#supportsMultipleResultSets()
303
      // because Oracle driver (incorrectly) returns false
304

305
      // Crazy Standard JDBC way of determining if there are more results
306
      // DO NOT try to 'improve' the condition even if IDE tells you to!
307
      // It's important that getUpdateCount() is called here.
308
      if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
1✔
309
        ResultSet rs = stmt.getResultSet();
1✔
310
        if (rs == null) {
1!
311
          return getNextResultSet(stmt);
×
312
        } else {
313
          return new ResultSetWrapper(rs, configuration);
1✔
314
        }
315
      }
316
    } catch (Exception e) {
×
317
      // Intentionally ignored.
318
    }
1✔
319
    return null;
1✔
320
  }
321

322
  private void closeResultSet(ResultSet rs) {
323
    try {
324
      if (rs != null) {
1!
325
        rs.close();
1✔
326
      }
327
    } catch (SQLException e) {
×
328
      // ignore
329
    }
1✔
330
  }
1✔
331

332
  private void cleanUpAfterHandlingResultSet() {
333
    nestedResultObjects.clear();
1✔
334
  }
1✔
335

336
  private void validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount) {
337
    if (rsw != null && resultMapCount < 1) {
1✔
338
      throw new ExecutorException(
1✔
339
          "A query was run and no Result Maps were found for the Mapped Statement '" + mappedStatement.getId()
1✔
340
              + "'. 'resultType' or 'resultMap' must be specified when there is no corresponding method.");
341
    }
342
  }
1✔
343

344
  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults,
345
      ResultMapping parentMapping) throws SQLException {
346
    try {
347
      if (parentMapping != null) {
1✔
348
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
1✔
349
      } else if (resultHandler == null) {
1✔
350
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
1✔
351
        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
1✔
352
        multipleResults.add(defaultResultHandler.getResultList());
1✔
353
      } else {
1✔
354
        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
1✔
355
      }
356
    } finally {
357
      // issue #228 (close resultsets)
358
      closeResultSet(rsw.getResultSet());
1✔
359
    }
360
  }
1✔
361

362
  @SuppressWarnings("unchecked")
363
  private List<Object> collapseSingleResultList(List<Object> multipleResults) {
364
    return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
1✔
365
  }
366

367
  //
368
  // HANDLE ROWS FOR SIMPLE RESULTMAP
369
  //
370

371
  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
372
      RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
373
    if (resultMap.hasNestedResultMaps()) {
1✔
374
      ensureNoRowBounds();
1✔
375
      checkResultHandler();
1✔
376
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
1✔
377
    } else {
378
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
1✔
379
    }
380
  }
1✔
381

382
  private void ensureNoRowBounds() {
383
    if (configuration.isSafeRowBoundsEnabled() && rowBounds != null
1!
384
        && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) {
×
385
      throw new ExecutorException(
×
386
          "Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. "
387
              + "Use safeRowBoundsEnabled=false setting to bypass this check.");
388
    }
389
  }
1✔
390

391
  protected void checkResultHandler() {
392
    if (resultHandler != null && configuration.isSafeResultHandlerEnabled() && !mappedStatement.isResultOrdered()) {
1!
393
      throw new ExecutorException(
1✔
394
          "Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler. "
395
              + "Use safeResultHandlerEnabled=false setting to bypass this check "
396
              + "or ensure your statement returns ordered data and set resultOrdered=true on it.");
397
    }
398
  }
1✔
399

400
  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
401
      ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
402
    final boolean useCollectionConstructorInjection = resultMap.hasResultMapsUsingConstructorCollection();
1✔
403

404
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
1✔
405
    ResultSet resultSet = rsw.getResultSet();
1✔
406
    skipRows(resultSet, rowBounds);
1✔
407
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
1!
408
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw, resultMap, null);
1✔
409
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null, null);
1✔
410
      if (!useCollectionConstructorInjection) {
1!
411
        storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
412
      } else {
413
        if (!(rowValue instanceof PendingConstructorCreation)) {
×
414
          throw new ExecutorException("Expected result object to be a pending constructor creation!");
×
415
        }
416

417
        createAndStorePendingCreation(resultHandler, resultSet, resultContext, (PendingConstructorCreation) rowValue);
×
418
      }
419
    }
1✔
420
  }
1✔
421

422
  private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue,
423
      ResultMapping parentMapping, ResultSet rs) throws SQLException {
424
    if (parentMapping != null) {
1✔
425
      linkToParents(rs, parentMapping, rowValue);
1✔
426
      return;
1✔
427
    }
428

429
    if (pendingPccRelations.containsKey(rowValue)) {
1✔
430
      createPendingConstructorCreations(rowValue);
1✔
431
    }
432

433
    callResultHandler(resultHandler, resultContext, rowValue);
1✔
434
  }
1✔
435

436
  @SuppressWarnings("unchecked" /* because ResultHandler<?> is always ResultHandler<Object> */)
437
  private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext,
438
      Object rowValue) {
439
    resultContext.nextResultObject(rowValue);
1✔
440
    ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
1✔
441
  }
1✔
442

443
  private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
444
    return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
1✔
445
  }
446

447
  private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
448
    if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
1✔
449
      if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
1!
450
        rs.absolute(rowBounds.getOffset());
1✔
451
      }
452
    } else {
453
      for (int i = 0; i < rowBounds.getOffset(); i++) {
1✔
454
        if (!rs.next()) {
1!
455
          break;
×
456
        }
457
      }
458
    }
459
  }
1✔
460

461
  //
462
  // GET VALUE FROM ROW FOR SIMPLE RESULT MAP
463
  //
464

465
  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, CacheKey parentRowKey)
466
      throws SQLException {
467
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
1✔
468
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix, parentRowKey);
1✔
469
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
1✔
470
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
1✔
471
      boolean foundValues = this.useConstructorMappings;
1✔
472
      if (shouldApplyAutomaticMappings(resultMap, false)) {
1✔
473
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
1✔
474
      }
475
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
1✔
476
      foundValues = lazyLoader.size() > 0 || foundValues;
1✔
477
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
1✔
478
    }
479

480
    if (parentRowKey != null) {
1✔
481
      // found a simple object/primitive in pending constructor creation that will need linking later
482
      final CacheKey rowKey = createRowKey(resultMap, rsw, columnPrefix);
1✔
483
      final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
484

485
      if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
486
        nestedResultObjects.put(combinedKey, rowValue);
1✔
487
      }
488
    }
489

490
    return rowValue;
1✔
491
  }
492

493
  //
494
  // GET VALUE FROM ROW FOR NESTED RESULT MAP
495
  //
496

497
  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix,
498
      Object partialObject) throws SQLException {
499
    final String resultMapId = resultMap.getId();
1✔
500
    Object rowValue = partialObject;
1✔
501
    if (rowValue != null) {
1✔
502
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
1✔
503
      putAncestor(rowValue, resultMapId);
1✔
504
      applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
1✔
505
      ancestorObjects.remove(resultMapId);
1✔
506
    } else {
1✔
507
      final ResultLoaderMap lazyLoader = new ResultLoaderMap();
1✔
508
      rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix, combinedKey);
1✔
509
      if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
1✔
510
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
1✔
511
        boolean foundValues = this.useConstructorMappings;
1✔
512
        if (shouldApplyAutomaticMappings(resultMap, true)) {
1✔
513
          foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
1!
514
        }
515
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
1✔
516
        putAncestor(rowValue, resultMapId);
1✔
517
        foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true)
1✔
518
            || foundValues;
519
        ancestorObjects.remove(resultMapId);
1✔
520
        foundValues = lazyLoader.size() > 0 || foundValues;
1✔
521
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
1✔
522
      }
523
      if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
524
        nestedResultObjects.put(combinedKey, rowValue);
1✔
525
      }
526
    }
527
    return rowValue;
1✔
528
  }
529

530
  private void putAncestor(Object resultObject, String resultMapId) {
531
    ancestorObjects.put(resultMapId, resultObject);
1✔
532
  }
1✔
533

534
  private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
535
    if (resultMap.getAutoMapping() != null) {
1✔
536
      return resultMap.getAutoMapping();
1✔
537
    }
538
    if (isNested) {
1✔
539
      return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
1✔
540
    } else {
541
      return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
1✔
542
    }
543
  }
544

545
  //
546
  // PROPERTY MAPPINGS
547
  //
548

549
  private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
550
      ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
551
    final Set<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
1✔
552
    boolean foundValues = false;
1✔
553
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
1✔
554
    for (ResultMapping propertyMapping : propertyMappings) {
1✔
555
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
1✔
556
      if (propertyMapping.getNestedResultMapId() != null && !JdbcType.CURSOR.equals(propertyMapping.getJdbcType())) {
1!
557
        // the user added a column attribute to a nested result map, ignore it
558
        column = null;
1✔
559
      }
560
      if (propertyMapping.isCompositeResult()
1✔
561
          || column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))
1✔
562
          || propertyMapping.getResultSet() != null) {
1✔
563
        Object value = getPropertyMappingValue(rsw, metaObject, propertyMapping, lazyLoader, columnPrefix);
1✔
564
        // issue #541 make property optional
565
        final String property = propertyMapping.getProperty();
1✔
566
        if (property == null) {
1✔
567
          continue;
1✔
568
        }
569
        if (value == DEFERRED) {
1✔
570
          foundValues = true;
1✔
571
          continue;
1✔
572
        }
573
        if (value != null) {
1✔
574
          foundValues = true;
1✔
575
        }
576
        if (value != null
1✔
577
            || configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()) {
1!
578
          // gcode issue #377, call setter on nulls (value is not 'found')
579
          metaObject.setValue(property, value);
1✔
580
        }
581
      }
582
    }
1✔
583
    return foundValues;
1✔
584
  }
585

586
  private Object getPropertyMappingValue(ResultSetWrapper rsw, MetaObject metaResultObject,
587
      ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
588
    final ResultSet rs = rsw.getResultSet();
1✔
589
    final String property = propertyMapping.getProperty();
1✔
590
    if (propertyMapping.getNestedQueryId() != null) {
1✔
591
      return getNestedQueryMappingValue(rsw, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
1✔
592
    }
593
    if (JdbcType.CURSOR.equals(propertyMapping.getJdbcType())) {
1!
NEW
594
      List<Object> results = getNestedCursorValue(rsw, propertyMapping, columnPrefix);
×
595
      linkObjects(metaResultObject, propertyMapping, results.get(0), true);
×
596
      return metaResultObject.getValue(propertyMapping.getProperty());
×
597
    }
598
    if (propertyMapping.getResultSet() != null) {
1✔
599
      addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
1✔
600
      return DEFERRED;
1✔
601
    } else {
602
      final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
1✔
603
      TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
1✔
604
      if (typeHandler == null) {
1✔
605
        typeHandler = resolvePropertyTypeHandler(rsw, metaResultObject, property, column);
1✔
606
      }
607
      return typeHandler.getResult(rs, column);
1✔
608
    }
609
  }
610

611
  private List<Object> getNestedCursorValue(ResultSetWrapper rsw, ResultMapping propertyMapping,
612
      String parentColumnPrefix) throws SQLException {
613
    final String column = prependPrefix(propertyMapping.getColumn(), parentColumnPrefix);
×
NEW
614
    ResultMap nestedResultMap = resolveDiscriminatedResultMap(rsw,
×
615
        configuration.getResultMap(propertyMapping.getNestedResultMapId()),
×
616
        getColumnPrefix(parentColumnPrefix, propertyMapping));
×
NEW
617
    ResultSetWrapper nestedRsw = new ResultSetWrapper(rsw.getResultSet().getObject(column, ResultSet.class),
×
618
        configuration);
619
    List<Object> results = new ArrayList<>();
×
NEW
620
    handleResultSet(nestedRsw, nestedResultMap, results, null);
×
621
    return results;
×
622
  }
623

624
  private TypeHandler<?> resolvePropertyTypeHandler(ResultSetWrapper rsw, MetaObject metaResultObject,
625
      final String property, final String column) {
626
    CacheKey typeHandlerCacheKey = new CacheKey();
1✔
627
    Class<?> metaResultObjectClass = metaResultObject.getOriginalObject().getClass();
1✔
628
    typeHandlerCacheKey.update(metaResultObjectClass);
1✔
629
    typeHandlerCacheKey.update(column);
1✔
630
    typeHandlerCacheKey.update(property);
1✔
631
    return typeHandlerCache.computeIfAbsent(typeHandlerCacheKey, k -> {
1✔
632
      final JdbcType jdbcType = rsw.getJdbcType(column);
1✔
633
      final TypeHandler<?> th;
634
      if (property == null) {
1✔
635
        th = typeHandlerRegistry.getTypeHandler(jdbcType);
1✔
636
      } else {
637
        Type classToHandle = metaResultObject.getGenericSetterType(property).getKey();
1✔
638
        th = configuration.getTypeHandlerRegistry().resolve(metaResultObjectClass, classToHandle, jdbcType, null);
1✔
639
        if (th == null) {
1✔
640
          throw new TypeException(
1✔
641
              "No usable type handler found for mapping the result of column '" + column + "' to property '" + property
642
                  + "'. It was either not specified and/or could not be found for the javaType (" + classToHandle
643
                  + ") : jdbcType (" + jdbcType + ") combination.");
644
        }
645
      }
646
      return th;
1✔
647
    });
648
  }
649

650
  private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap,
651
      MetaObject metaObject, String columnPrefix) throws SQLException {
652
    final String mapKey = resultMap.getId() + ":" + columnPrefix;
1✔
653
    List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
1✔
654
    if (autoMapping == null) {
1✔
655
      autoMapping = new ArrayList<>();
1✔
656
      final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
1✔
657
      // Remove the entry to release the memory
658
      List<String> mappedInConstructorAutoMapping = constructorAutoMappingColumns.remove(mapKey);
1✔
659
      if (mappedInConstructorAutoMapping != null) {
1✔
660
        unmappedColumnNames.removeAll(mappedInConstructorAutoMapping);
1✔
661
      }
662
      for (String columnName : unmappedColumnNames) {
1✔
663
        String propertyName = columnName;
1✔
664
        if (columnPrefix != null && !columnPrefix.isEmpty()) {
1!
665
          // When columnPrefix is specified,
666
          // ignore columns without the prefix.
667
          if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
668
            continue;
1✔
669
          }
670
          propertyName = columnName.substring(columnPrefix.length());
1✔
671
        }
672
        final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
1✔
673
        if (property != null && metaObject.hasSetter(property)) {
1✔
674
          if (resultMap.getMappedProperties().contains(property)) {
1✔
675
            continue;
1✔
676
          }
677
          final Type propertyType = metaObject.getGenericSetterType(property).getKey();
1✔
678
          Class<?> metaObjectClass = metaObject.getOriginalObject().getClass();
1✔
679
          TypeHandler<?> typeHandler = configuration.getTypeHandlerRegistry().resolve(metaObjectClass, propertyType,
1✔
680
              rsw.getJdbcType(columnName), null);
1✔
681
          if (typeHandler != null) {
1✔
682
            autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler,
1✔
683
                propertyType instanceof Class && ((Class<?>) propertyType).isPrimitive()));
1✔
684
          } else {
685
            configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property,
1✔
686
                propertyType);
687
          }
688
        } else {
1✔
689
          configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName,
1✔
690
              property != null ? property : propertyName, null);
1✔
691
        }
692
      }
1✔
693
      autoMappingsCache.put(mapKey, autoMapping);
1✔
694
    }
695
    return autoMapping;
1✔
696
  }
697

698
  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
699
      String columnPrefix) throws SQLException {
700
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
1✔
701
    boolean foundValues = false;
1✔
702
    if (!autoMapping.isEmpty()) {
1✔
703
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
1✔
704
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
1✔
705
        if (value != null) {
1✔
706
          foundValues = true;
1✔
707
        }
708
        if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) {
1!
709
          // gcode issue #377, call setter on nulls (value is not 'found')
710
          metaObject.setValue(mapping.property, value);
1✔
711
        }
712
      }
1✔
713
    }
714
    return foundValues;
1✔
715
  }
716

717
  // MULTIPLE RESULT SETS
718

719
  private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException {
720
    CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(),
1✔
721
        parentMapping.getForeignColumn());
1✔
722
    List<PendingRelation> parents = pendingRelations.get(parentKey);
1✔
723
    if (parents != null) {
1✔
724
      for (PendingRelation parent : parents) {
1✔
725
        if (parent != null && rowValue != null) {
1!
726
          linkObjects(parent.metaObject, parent.propertyMapping, rowValue);
1✔
727
        }
728
      }
1✔
729
    }
730
  }
1✔
731

732
  private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, ResultMapping parentMapping)
733
      throws SQLException {
734
    CacheKey cacheKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(),
1✔
735
        parentMapping.getColumn());
1✔
736
    PendingRelation deferLoad = new PendingRelation();
1✔
737
    deferLoad.metaObject = metaResultObject;
1✔
738
    deferLoad.propertyMapping = parentMapping;
1✔
739
    List<PendingRelation> relations = pendingRelations.computeIfAbsent(cacheKey, k -> new ArrayList<>());
1✔
740
    // issue #255
741
    relations.add(deferLoad);
1✔
742
    ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet());
1✔
743
    if (previous == null) {
1✔
744
      nextResultMaps.put(parentMapping.getResultSet(), parentMapping);
1✔
745
    } else if (!previous.equals(parentMapping)) {
1!
746
      throw new ExecutorException("Two different properties are mapped to the same resultSet");
×
747
    }
748
  }
1✔
749

750
  private CacheKey createKeyForMultipleResults(ResultSet rs, ResultMapping resultMapping, String names, String columns)
751
      throws SQLException {
752
    CacheKey cacheKey = new CacheKey();
1✔
753
    cacheKey.update(resultMapping);
1✔
754
    if (columns != null && names != null) {
1!
755
      String[] columnsArray = columns.split(",");
1✔
756
      String[] namesArray = names.split(",");
1✔
757
      for (int i = 0; i < columnsArray.length; i++) {
1✔
758
        Object value = rs.getString(columnsArray[i]);
1✔
759
        if (value != null) {
1!
760
          cacheKey.update(namesArray[i]);
1✔
761
          cacheKey.update(value);
1✔
762
        }
763
      }
764
    }
765
    return cacheKey;
1✔
766
  }
767

768
  //
769
  // INSTANTIATION & CONSTRUCTOR MAPPING
770
  //
771

772
  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,
773
      String columnPrefix, CacheKey parentRowKey) throws SQLException {
774
    this.useConstructorMappings = false; // reset previous mapping result
1✔
775
    final List<Class<?>> constructorArgTypes = new ArrayList<>();
1✔
776
    final List<Object> constructorArgs = new ArrayList<>();
1✔
777

778
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix,
1✔
779
        parentRowKey);
780
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
1✔
781
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
1✔
782
      for (ResultMapping propertyMapping : propertyMappings) {
1✔
783
        // issue gcode #109 && issue #149
784
        if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
1✔
785
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration,
1✔
786
              objectFactory, constructorArgTypes, constructorArgs);
787
          break;
1✔
788
        }
789
      }
1✔
790

791
      // (issue #101)
792
      if (resultMap.hasResultMapsUsingConstructorCollection() && resultObject instanceof PendingConstructorCreation) {
1!
793
        linkNestedPendingCreations(rsw, resultMap, columnPrefix, parentRowKey,
1✔
794
            (PendingConstructorCreation) resultObject, constructorArgs);
795
      }
796
    }
797

798
    this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
1✔
799
    return resultObject;
1✔
800
  }
801

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

805
    final Class<?> resultType = resultMap.getType();
1✔
806
    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
1✔
807
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
1✔
808
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
1✔
809
      return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
1✔
810
    }
811
    if (!constructorMappings.isEmpty()) {
1✔
812
      return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs,
1✔
813
          columnPrefix, resultMap.hasResultMapsUsingConstructorCollection(), parentRowKey);
1✔
814
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
1✔
815
      return objectFactory.create(resultType);
1✔
816
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
1!
817
      return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes,
1✔
818
          constructorArgs);
819
    }
820
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
×
821
  }
822

823
  Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType,
824
      List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
825
      String columnPrefix, boolean useCollectionConstructorInjection, CacheKey parentRowKey) {
826
    boolean foundValues = false;
1✔
827

828
    for (ResultMapping constructorMapping : constructorMappings) {
1✔
829
      final Class<?> parameterType = constructorMapping.getJavaType();
1✔
830
      final String column = constructorMapping.getColumn();
1✔
831
      final Object value;
832
      try {
833
        if (constructorMapping.getNestedQueryId() != null) {
1✔
834
          value = getNestedQueryConstructorValue(rsw, constructorMapping, columnPrefix);
1✔
835
        } else if (JdbcType.CURSOR.equals(constructorMapping.getJdbcType())) {
1!
NEW
836
          List<?> result = (List<?>) getNestedCursorValue(rsw, constructorMapping, columnPrefix).get(0);
×
837
          if (objectFactory.isCollection(parameterType)) {
×
838
            MetaObject collection = configuration.newMetaObject(objectFactory.create(parameterType));
×
839
            collection.addAll((List<?>) result);
×
840
            value = collection.getOriginalObject();
×
841
          } else {
×
842
            value = toSingleObj(result);
×
843
          }
844
        } else if (constructorMapping.getNestedResultMapId() != null) {
1✔
845
          final String constructorColumnPrefix = getColumnPrefix(columnPrefix, constructorMapping);
1✔
846
          final ResultMap resultMap = resolveDiscriminatedResultMap(rsw,
1✔
847
              configuration.getResultMap(constructorMapping.getNestedResultMapId()), constructorColumnPrefix);
1✔
848
          value = getRowValue(rsw, resultMap, constructorColumnPrefix,
1✔
849
              useCollectionConstructorInjection ? parentRowKey : null);
1✔
850
        } else {
1✔
851
          TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
1✔
852
          if (typeHandler == null) {
1✔
853
            typeHandler = typeHandlerRegistry.getTypeHandler(constructorMapping.getJavaType(), rsw.getJdbcType(column));
1✔
854
          }
855
          value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
1✔
856
        }
857
      } catch (ResultMapException | SQLException e) {
1✔
858
        throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
1✔
859
      }
1✔
860

861
      constructorArgTypes.add(parameterType);
1✔
862
      constructorArgs.add(value);
1✔
863

864
      foundValues = value != null || foundValues;
1✔
865
    }
1✔
866

867
    if (!foundValues) {
1✔
868
      return null;
1✔
869
    }
870

871
    if (useCollectionConstructorInjection) {
1✔
872
      // at least one of the nestedResultMaps contained a collection, we have to defer until later
873
      return new PendingConstructorCreation(resultType, constructorArgTypes, constructorArgs);
1✔
874
    }
875

876
    return objectFactory.create(resultType, constructorArgTypes, constructorArgs);
1✔
877
  }
878

879
  private Object createByConstructorSignature(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
880
      Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
881
    return applyConstructorAutomapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs,
1✔
882
        findConstructorForAutomapping(resultType, rsw).orElseThrow(() -> new ExecutorException(
1✔
883
            "No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames())));
×
884
  }
885

886
  private Optional<Constructor<?>> findConstructorForAutomapping(final Class<?> resultType, ResultSetWrapper rsw) {
887
    Constructor<?>[] constructors = resultType.getDeclaredConstructors();
1✔
888
    if (constructors.length == 1) {
1✔
889
      return Optional.of(constructors[0]);
1✔
890
    }
891
    Optional<Constructor<?>> annotated = Arrays.stream(constructors)
1✔
892
        .filter(x -> x.isAnnotationPresent(AutomapConstructor.class)).reduce((x, y) -> {
1✔
893
          throw new ExecutorException("@AutomapConstructor should be used in only one constructor.");
1✔
894
        });
895
    if (annotated.isPresent()) {
1✔
896
      return annotated;
1✔
897
    }
898
    if (configuration.isArgNameBasedConstructorAutoMapping()) {
1!
899
      // Finding-best-match type implementation is possible,
900
      // but using @AutomapConstructor seems sufficient.
901
      throw new ExecutorException(MessageFormat.format(
×
902
          "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.",
903
          resultType.getName()));
×
904
    } else {
905
      return Arrays.stream(constructors).filter(x -> findUsableConstructorByArgTypes(x, rsw.getJdbcTypes())).findAny();
1✔
906
    }
907
  }
908

909
  private boolean findUsableConstructorByArgTypes(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) {
910
    final Class<?>[] parameterTypes = constructor.getParameterTypes();
1✔
911
    if (parameterTypes.length != jdbcTypes.size()) {
1✔
912
      return false;
1✔
913
    }
914
    for (int i = 0; i < parameterTypes.length; i++) {
1✔
915
      if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) {
1!
916
        return false;
×
917
      }
918
    }
919
    return true;
1✔
920
  }
921

922
  private Object applyConstructorAutomapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
923
      Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor)
924
      throws SQLException {
925
    boolean foundValues = false;
1✔
926
    if (configuration.isArgNameBasedConstructorAutoMapping()) {
1✔
927
      foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, constructorArgTypes,
1✔
928
          constructorArgs, constructor, foundValues);
929
    } else {
930
      foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor,
1✔
931
          foundValues);
932
    }
933
    return foundValues || configuration.isReturnInstanceForEmptyRow()
1!
934
        ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
1✔
935
  }
936

937
  private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,
938
      List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {
939
    Class<?>[] parameterTypes = constructor.getParameterTypes();
1✔
940

941
    if (parameterTypes.length > rsw.getClassNames().size()) {
1✔
942
      throw new ExecutorException(MessageFormat.format(
1✔
943
          "Constructor auto-mapping of ''{0}'' failed. The constructor takes ''{1}'' arguments, but there are only ''{2}'' columns in the result set.",
944
          constructor, parameterTypes.length, rsw.getClassNames().size()));
1✔
945
    }
946

947
    for (int i = 0; i < parameterTypes.length; i++) {
1✔
948
      Class<?> parameterType = parameterTypes[i];
1✔
949
      String columnName = rsw.getColumnNames().get(i);
1✔
950
      TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
1✔
951
      Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
952
      constructorArgTypes.add(parameterType);
1✔
953
      constructorArgs.add(value);
1✔
954
      foundValues = value != null || foundValues;
1!
955
    }
956
    return foundValues;
1✔
957
  }
958

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

999
  private boolean columnMatchesParam(String columnName, String paramName, String columnPrefix) {
1000
    if (columnPrefix != null) {
1✔
1001
      if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
1002
        return false;
1✔
1003
      }
1004
      columnName = columnName.substring(columnPrefix.length());
1✔
1005
    }
1006
    return paramName
1✔
1007
        .equalsIgnoreCase(configuration.isMapUnderscoreToCamelCase() ? columnName.replace("_", "") : columnName);
1✔
1008
  }
1009

1010
  private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
1011
      throws SQLException {
1012
    final Class<?> resultType = resultMap.getType();
1✔
1013
    final String columnName;
1014
    if (!resultMap.getResultMappings().isEmpty()) {
1✔
1015
      final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
1✔
1016
      final ResultMapping mapping = resultMappingList.get(0);
1✔
1017
      columnName = prependPrefix(mapping.getColumn(), columnPrefix);
1✔
1018
    } else {
1✔
1019
      columnName = rsw.getColumnNames().get(0);
1✔
1020
    }
1021
    final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
1✔
1022
    return typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
1023
  }
1024

1025
  //
1026
  // NESTED QUERY
1027
  //
1028

1029
  private Object getNestedQueryConstructorValue(ResultSetWrapper rsw, ResultMapping constructorMapping,
1030
      String columnPrefix) throws SQLException {
1031
    final String nestedQueryId = constructorMapping.getNestedQueryId();
1✔
1032
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
1✔
1033
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
1✔
1034
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rsw, constructorMapping,
1✔
1035
        nestedQueryParameterType, columnPrefix);
1036
    Object value = null;
1✔
1037
    if (nestedQueryParameterObject != null) {
1!
1038
      final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
1✔
1039
      final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
1✔
1040
          nestedBoundSql);
1041
      final Class<?> targetType = constructorMapping.getJavaType();
1✔
1042
      final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
1✔
1043
          nestedQueryParameterObject, targetType, key, nestedBoundSql);
1044
      value = resultLoader.loadResult();
1✔
1045
    }
1046
    return value;
1✔
1047
  }
1048

1049
  private Object getNestedQueryMappingValue(ResultSetWrapper rsw, MetaObject metaResultObject,
1050
      ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
1051
    final String nestedQueryId = propertyMapping.getNestedQueryId();
1✔
1052
    final String property = propertyMapping.getProperty();
1✔
1053
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
1✔
1054
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
1✔
1055
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rsw, propertyMapping,
1✔
1056
        nestedQueryParameterType, columnPrefix);
1057
    Object value = null;
1✔
1058
    if (nestedQueryParameterObject != null) {
1✔
1059
      final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
1✔
1060
      final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
1✔
1061
          nestedBoundSql);
1062
      final Class<?> targetType = propertyMapping.getJavaType();
1✔
1063
      if (executor.isCached(nestedQuery, key)) {
1✔
1064
        executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
1✔
1065
        value = DEFERRED;
1✔
1066
      } else {
1067
        final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
1✔
1068
            nestedQueryParameterObject, targetType, key, nestedBoundSql);
1069
        if (propertyMapping.isLazy()) {
1✔
1070
          lazyLoader.addLoader(property, metaResultObject, resultLoader);
1✔
1071
          value = DEFERRED;
1✔
1072
        } else {
1073
          value = resultLoader.loadResult();
1✔
1074
        }
1075
      }
1076
    }
1077
    return value;
1✔
1078
  }
1079

1080
  private Object prepareParameterForNestedQuery(ResultSetWrapper rsw, ResultMapping resultMapping,
1081
      Class<?> parameterType, String columnPrefix) throws SQLException {
1082
    if (resultMapping.isCompositeResult()) {
1✔
1083
      return prepareCompositeKeyParameter(rsw, resultMapping, parameterType, columnPrefix);
1✔
1084
    }
1085
    return prepareSimpleKeyParameter(rsw, resultMapping, parameterType, columnPrefix);
1✔
1086
  }
1087

1088
  private Object prepareSimpleKeyParameter(ResultSetWrapper rsw, ResultMapping resultMapping, Class<?> parameterType,
1089
      String columnPrefix) throws SQLException {
1090
    // parameterType is ignored in this case
1091
    final String columnName = prependPrefix(resultMapping.getColumn(), columnPrefix);
1✔
1092
    final TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
1✔
1093
    return typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
1094
  }
1095

1096
  private Object prepareCompositeKeyParameter(ResultSetWrapper rsw, ResultMapping resultMapping, Class<?> parameterType,
1097
      String columnPrefix) throws SQLException {
1098
    // Map is used if parameterType is not specified
1099
    final Object parameterObject = instantiateParameterObject(parameterType);
1✔
1100
    final MetaObject metaObject = configuration.newMetaObject(parameterObject);
1✔
1101
    boolean foundValues = false;
1✔
1102
    for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
1✔
1103
      final String columnName = prependPrefix(innerResultMapping.getColumn(), columnPrefix);
1✔
1104
      final TypeHandler<?> typeHandler = resolvePropertyTypeHandler(rsw, metaObject, innerResultMapping.getColumn(),
1✔
1105
          columnPrefix);
1106
      final Object propValue = typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
1107
      // issue #353 & #560 do not execute nested query if key is null
1108
      if (propValue != null) {
1✔
1109
        metaObject.setValue(innerResultMapping.getProperty(), propValue);
1✔
1110
        foundValues = true;
1✔
1111
      }
1112
    }
1✔
1113
    return foundValues ? parameterObject : null;
1✔
1114
  }
1115

1116
  private Object instantiateParameterObject(Class<?> parameterType) {
1117
    if (parameterType == null) {
1✔
1118
      return new HashMap<>();
1✔
1119
    }
1120
    if (ParamMap.class.equals(parameterType)) {
1✔
1121
      return new HashMap<>(); // issue #649
1✔
1122
    } else {
1123
      return objectFactory.create(parameterType);
1✔
1124
    }
1125
  }
1126

1127
  //
1128
  // DISCRIMINATOR
1129
  //
1130

1131
  public ResultMap resolveDiscriminatedResultMap(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
1132
      throws SQLException {
1133
    Set<String> pastDiscriminators = new HashSet<>();
1✔
1134
    Discriminator discriminator = resultMap.getDiscriminator();
1✔
1135
    while (discriminator != null) {
1✔
1136
      final Object value = getDiscriminatorValue(rsw, discriminator, columnPrefix);
1✔
1137
      final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
1✔
1138
      if (!configuration.hasResultMap(discriminatedMapId)) {
1✔
1139
        break;
1✔
1140
      }
1141
      resultMap = configuration.getResultMap(discriminatedMapId);
1✔
1142
      Discriminator lastDiscriminator = discriminator;
1✔
1143
      discriminator = resultMap.getDiscriminator();
1✔
1144
      if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
1!
1145
        break;
1✔
1146
      }
1147
    }
1✔
1148
    return resultMap;
1✔
1149
  }
1150

1151
  private Object getDiscriminatorValue(ResultSetWrapper rsw, Discriminator discriminator, String columnPrefix)
1152
      throws SQLException {
1153
    final ResultMapping resultMapping = discriminator.getResultMapping();
1✔
1154
    String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
1✔
1155
    TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
1✔
1156
    if (typeHandler == null) {
1✔
1157
      typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.getJavaType(), rsw.getJdbcType(column));
1✔
1158
    }
1159
    return typeHandler.getResult(rsw.getResultSet(), column);
1✔
1160
  }
1161

1162
  private String prependPrefix(String columnName, String prefix) {
1163
    if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
1!
1164
      return columnName;
1✔
1165
    }
1166
    return prefix + columnName;
1✔
1167
  }
1168

1169
  //
1170
  // HANDLE NESTED RESULT MAPS
1171
  //
1172

1173
  private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap,
1174
      ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
1175
    final boolean useCollectionConstructorInjection = resultMap.hasResultMapsUsingConstructorCollection();
1✔
1176
    PendingConstructorCreation lastHandledCreation = null;
1✔
1177
    if (useCollectionConstructorInjection) {
1✔
1178
      verifyPendingCreationPreconditions(parentMapping);
1✔
1179
    }
1180

1181
    final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
1✔
1182
    ResultSet resultSet = rsw.getResultSet();
1✔
1183
    skipRows(resultSet, rowBounds);
1✔
1184
    Object rowValue = previousRowValue;
1✔
1185

1186
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
1!
1187
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw, resultMap, null);
1✔
1188
      final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
1✔
1189

1190
      final Object partialObject = nestedResultObjects.get(rowKey);
1✔
1191
      final boolean foundNewUniqueRow = partialObject == null;
1✔
1192

1193
      // issue #577, #542 && #101
1194
      if (useCollectionConstructorInjection) {
1✔
1195
        if (foundNewUniqueRow && lastHandledCreation != null) {
1✔
1196
          createAndStorePendingCreation(resultHandler, resultSet, resultContext, lastHandledCreation);
1✔
1197
          lastHandledCreation = null;
1✔
1198
        }
1199

1200
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1201
        if (rowValue instanceof PendingConstructorCreation) {
1!
1202
          lastHandledCreation = (PendingConstructorCreation) rowValue;
1✔
1203
        }
1204
      } else if (mappedStatement.isResultOrdered()) {
1✔
1205
        if (foundNewUniqueRow && rowValue != null) {
1✔
1206
          nestedResultObjects.clear();
1✔
1207
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1208
        }
1209
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1210
      } else {
1211
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1212
        if (foundNewUniqueRow) {
1✔
1213
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1214
        }
1215
      }
1216
    }
1✔
1217

1218
    if (useCollectionConstructorInjection && lastHandledCreation != null) {
1!
1219
      createAndStorePendingCreation(resultHandler, resultSet, resultContext, lastHandledCreation);
1✔
1220
    } else if (rowValue != null && mappedStatement.isResultOrdered()
1✔
1221
        && shouldProcessMoreRows(resultContext, rowBounds)) {
1✔
1222
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1223
      previousRowValue = null;
1✔
1224
    } else if (rowValue != null) {
1✔
1225
      previousRowValue = rowValue;
1✔
1226
    }
1227
  }
1✔
1228

1229
  //
1230
  // NESTED RESULT MAP (PENDING CONSTRUCTOR CREATIONS)
1231
  //
1232
  private void linkNestedPendingCreations(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
1233
      CacheKey parentRowKey, PendingConstructorCreation pendingCreation, List<Object> constructorArgs)
1234
      throws SQLException {
1235
    if (parentRowKey == null) {
1!
1236
      // nothing to link, possibly due to simple (non-nested) result map
1237
      return;
×
1238
    }
1239

1240
    final CacheKey rowKey = createRowKey(resultMap, rsw, columnPrefix);
1✔
1241
    final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
1242

1243
    if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1!
1244
      nestedResultObjects.put(combinedKey, pendingCreation);
1✔
1245
    }
1246

1247
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
1✔
1248
    for (int index = 0; index < constructorMappings.size(); index++) {
1✔
1249
      final ResultMapping constructorMapping = constructorMappings.get(index);
1✔
1250
      final String nestedResultMapId = constructorMapping.getNestedResultMapId();
1✔
1251

1252
      if (nestedResultMapId == null) {
1✔
1253
        continue;
1✔
1254
      }
1255

1256
      final Class<?> javaType = constructorMapping.getJavaType();
1✔
1257
      if (javaType == null || !objectFactory.isCollection(javaType)) {
1!
1258
        continue;
1✔
1259
      }
1260

1261
      final String constructorColumnPrefix = getColumnPrefix(columnPrefix, constructorMapping);
1✔
1262
      final ResultMap nestedResultMap = resolveDiscriminatedResultMap(rsw,
1✔
1263
          configuration.getResultMap(constructorMapping.getNestedResultMapId()), constructorColumnPrefix);
1✔
1264

1265
      final Object actualValue = constructorArgs.get(index);
1✔
1266
      final boolean hasValue = actualValue != null;
1✔
1267
      final boolean isInnerCreation = actualValue instanceof PendingConstructorCreation;
1✔
1268
      final boolean alreadyCreatedCollection = hasValue && objectFactory.isCollection(actualValue.getClass());
1!
1269

1270
      if (!isInnerCreation) {
1✔
1271
        final Collection<Object> value = pendingCreation.initializeCollectionForResultMapping(objectFactory,
1✔
1272
            nestedResultMap, constructorMapping, index);
1✔
1273
        if (!alreadyCreatedCollection) {
1!
1274
          // override values with empty collection
1275
          constructorArgs.set(index, value);
1✔
1276
        }
1277

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

1282
        if (nestedCombinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
1283
          nestedResultObjects.put(nestedCombinedKey, pendingCreation);
1✔
1284
        }
1285

1286
        if (hasValue) {
1✔
1287
          pendingCreation.linkCollectionValue(constructorMapping, actualValue);
1✔
1288
        }
1289
      } else {
1✔
1290
        final PendingConstructorCreation innerCreation = (PendingConstructorCreation) actualValue;
1✔
1291
        final Collection<Object> value = pendingCreation.initializeCollectionForResultMapping(objectFactory,
1✔
1292
            nestedResultMap, constructorMapping, index);
1✔
1293
        // we will fill this collection when building the final object
1294
        constructorArgs.set(index, value);
1✔
1295
        // link the creation for building later
1296
        pendingCreation.linkCreation(constructorMapping, innerCreation);
1✔
1297
      }
1298
    }
1299
  }
1✔
1300

1301
  private boolean applyNestedPendingConstructorCreations(ResultSetWrapper rsw, ResultMap resultMap,
1302
      MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject, boolean foundValues) {
1303
    if (newObject) {
1✔
1304
      // new objects are linked by createResultObject
1305
      return false;
1✔
1306
    }
1307

1308
    for (ResultMapping constructorMapping : resultMap.getConstructorResultMappings()) {
1✔
1309
      final String nestedResultMapId = constructorMapping.getNestedResultMapId();
1✔
1310
      final Class<?> parameterType = constructorMapping.getJavaType();
1✔
1311
      if (nestedResultMapId == null || constructorMapping.getResultSet() != null || parameterType == null
1!
1312
          || !objectFactory.isCollection(parameterType)) {
1✔
1313
        continue;
1✔
1314
      }
1315

1316
      try {
1317
        final String columnPrefix = getColumnPrefix(parentPrefix, constructorMapping);
1✔
1318
        final ResultMap nestedResultMap = getNestedResultMap(rsw, nestedResultMapId, columnPrefix);
1✔
1319

1320
        final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
1✔
1321
        final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
1322

1323
        // should have inserted already as a nested result object
1324
        Object rowValue = nestedResultObjects.get(combinedKey);
1✔
1325

1326
        PendingConstructorCreation pendingConstructorCreation = null;
1✔
1327
        if (rowValue instanceof PendingConstructorCreation) {
1✔
1328
          pendingConstructorCreation = (PendingConstructorCreation) rowValue;
1✔
1329
        } else if (rowValue != null) {
1✔
1330
          // found a simple object that was already linked/handled
1331
          continue;
1✔
1332
        }
1333

1334
        final boolean newValueForNestedResultMap = pendingConstructorCreation == null;
1✔
1335
        if (newValueForNestedResultMap) {
1✔
1336
          final Object parentObject = metaObject.getOriginalObject();
1✔
1337
          if (!(parentObject instanceof PendingConstructorCreation)) {
1!
1338
            throw new ExecutorException(
×
1339
                "parentObject is not a pending creation, cannot continue linking! MyBatis internal error!");
1340
          }
1341

1342
          pendingConstructorCreation = (PendingConstructorCreation) parentObject;
1✔
1343
        }
1344

1345
        rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix,
1✔
1346
            newValueForNestedResultMap ? null : pendingConstructorCreation);
1✔
1347

1348
        if (rowValue == null) {
1✔
1349
          continue;
1✔
1350
        }
1351

1352
        if (rowValue instanceof PendingConstructorCreation) {
1✔
1353
          if (newValueForNestedResultMap) {
1✔
1354
            // we created a brand new pcc. this is a new collection value
1355
            pendingConstructorCreation.linkCreation(constructorMapping, (PendingConstructorCreation) rowValue);
1✔
1356
            foundValues = true;
1✔
1357
          }
1358
        } else {
1359
          pendingConstructorCreation.linkCollectionValue(constructorMapping, rowValue);
1✔
1360
          foundValues = true;
1✔
1361

1362
          if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
1363
            nestedResultObjects.put(combinedKey, pendingConstructorCreation);
1✔
1364
          }
1365
        }
1366
      } catch (SQLException e) {
×
1367
        throw new ExecutorException("Error getting constructor collection nested result map values for '"
×
1368
            + constructorMapping.getProperty() + "'.  Cause: " + e, e);
×
1369
      }
1✔
1370
    }
1✔
1371

1372
    return foundValues;
1✔
1373
  }
1374

1375
  private void createPendingConstructorCreations(Object rowValue) {
1376
    // handle possible pending creations within this object
1377
    // by now, the property mapping has been completely built, we can reconstruct it
1378
    final PendingRelation pendingRelation = pendingPccRelations.remove(rowValue);
1✔
1379
    final MetaObject metaObject = pendingRelation.metaObject;
1✔
1380
    final ResultMapping resultMapping = pendingRelation.propertyMapping;
1✔
1381

1382
    // get the list to be built
1383
    Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1✔
1384
    if (collectionProperty != null) {
1!
1385
      // we expect pending creations now
1386
      final Collection<Object> pendingCreations = (Collection<Object>) collectionProperty;
1✔
1387

1388
      // remove the link to the old collection
1389
      metaObject.setValue(resultMapping.getProperty(), null);
1✔
1390

1391
      // create new collection property
1392
      collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1✔
1393
      final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1✔
1394

1395
      // create the pending objects
1396
      for (Object pendingCreation : pendingCreations) {
1✔
1397
        if (pendingCreation instanceof PendingConstructorCreation) {
1!
1398
          final PendingConstructorCreation pendingConstructorCreation = (PendingConstructorCreation) pendingCreation;
1✔
1399
          targetMetaObject.add(pendingConstructorCreation.create(objectFactory));
1✔
1400
        }
1401
      }
1✔
1402
    }
1403
  }
1✔
1404

1405
  private void verifyPendingCreationPreconditions(ResultMapping parentMapping) {
1406
    if (parentMapping != null) {
1!
1407
      throw new ExecutorException(
×
1408
          "Cannot construct objects with collections in constructors using multiple result sets yet!");
1409
    }
1410

1411
    if (!mappedStatement.isResultOrdered()) {
1!
1412
      throw new ExecutorException("Cannot reliably construct result if we are not sure the results are ordered "
×
1413
          + "so that no new previous rows would occur, set resultOrdered on your mapped statement if you have verified this");
1414
    }
1415
  }
1✔
1416

1417
  private void createAndStorePendingCreation(ResultHandler<?> resultHandler, ResultSet resultSet,
1418
      DefaultResultContext<Object> resultContext, PendingConstructorCreation pendingCreation) throws SQLException {
1419
    final Object result = pendingCreation.create(objectFactory);
1✔
1420
    storeObject(resultHandler, resultContext, result, null, resultSet);
1✔
1421
    nestedResultObjects.clear();
1✔
1422
  }
1✔
1423

1424
  //
1425
  // NESTED RESULT MAP (JOIN MAPPING)
1426
  //
1427

1428
  private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
1429
      String parentPrefix, CacheKey parentRowKey, boolean newObject) {
1430
    boolean foundValues = false;
1✔
1431
    for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
1✔
1432
      final String nestedResultMapId = resultMapping.getNestedResultMapId();
1✔
1433
      if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
1!
1434
        try {
1435
          final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
1✔
1436
          final ResultMap nestedResultMap = getNestedResultMap(rsw, nestedResultMapId, columnPrefix);
1✔
1437
          if (resultMapping.getColumnPrefix() == null) {
1✔
1438
            // try to fill circular reference only when columnPrefix
1439
            // is not specified for the nested result map (issue #215)
1440
            Object ancestorObject = ancestorObjects.get(nestedResultMapId);
1✔
1441
            if (ancestorObject != null) {
1✔
1442
              if (newObject) {
1✔
1443
                linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
1✔
1444
              }
1445
              continue;
1✔
1446
            }
1447
          }
1448
          final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
1✔
1449
          final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
1450
          Object rowValue = nestedResultObjects.get(combinedKey);
1✔
1451
          boolean knownValue = rowValue != null;
1✔
1452
          instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
1✔
1453
          if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
1✔
1454
            rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
1✔
1455
            if (rowValue != null && !knownValue) {
1✔
1456
              linkObjects(metaObject, resultMapping, rowValue);
1✔
1457
              foundValues = true;
1✔
1458
            }
1459
          }
1460
        } catch (SQLException e) {
×
1461
          throw new ExecutorException(
×
1462
              "Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
×
1463
        }
1✔
1464
      }
1465
    }
1✔
1466

1467
    // (issue #101)
1468
    if (resultMap.hasResultMapsUsingConstructorCollection()) {
1✔
1469
      foundValues = applyNestedPendingConstructorCreations(rsw, resultMap, metaObject, parentPrefix, parentRowKey,
1✔
1470
          newObject, foundValues);
1471
    }
1472

1473
    return foundValues;
1✔
1474
  }
1475

1476
  private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) {
1477
    final StringBuilder columnPrefixBuilder = new StringBuilder();
1✔
1478
    if (parentPrefix != null) {
1✔
1479
      columnPrefixBuilder.append(parentPrefix);
1✔
1480
    }
1481
    if (resultMapping.getColumnPrefix() != null) {
1✔
1482
      columnPrefixBuilder.append(resultMapping.getColumnPrefix());
1✔
1483
    }
1484
    return columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH);
1✔
1485
  }
1486

1487
  private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSetWrapper rsw)
1488
      throws SQLException {
1489
    Set<String> notNullColumns = resultMapping.getNotNullColumns();
1✔
1490
    if (notNullColumns != null && !notNullColumns.isEmpty()) {
1✔
1491
      ResultSet rs = rsw.getResultSet();
1✔
1492
      for (String column : notNullColumns) {
1✔
1493
        rs.getObject(prependPrefix(column, columnPrefix));
1✔
1494
        if (!rs.wasNull()) {
1✔
1495
          return true;
1✔
1496
        }
1497
      }
1✔
1498
      return false;
1✔
1499
    }
1500
    if (columnPrefix != null) {
1✔
1501
      for (String columnName : rsw.getColumnNames()) {
1✔
1502
        if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix.toUpperCase(Locale.ENGLISH))) {
1✔
1503
          return true;
1✔
1504
        }
1505
      }
1✔
1506
      return false;
1✔
1507
    }
1508
    return true;
1✔
1509
  }
1510

1511
  private ResultMap getNestedResultMap(ResultSetWrapper rsw, String nestedResultMapId, String columnPrefix)
1512
      throws SQLException {
1513
    ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId);
1✔
1514
    return resolveDiscriminatedResultMap(rsw, nestedResultMap, columnPrefix);
1✔
1515
  }
1516

1517
  //
1518
  // UNIQUE RESULT KEY
1519
  //
1520

1521
  private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
1522
    final CacheKey cacheKey = new CacheKey();
1✔
1523
    cacheKey.update(resultMap.getId());
1✔
1524
    List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
1✔
1525
    if (resultMappings.isEmpty()) {
1✔
1526
      if (Map.class.isAssignableFrom(resultMap.getType())) {
1!
1527
        createRowKeyForMap(rsw, cacheKey);
×
1528
      } else {
1529
        createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
1✔
1530
      }
1531
    } else {
1532
      createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
1✔
1533
    }
1534
    if (cacheKey.getUpdateCount() < 2) {
1✔
1535
      return CacheKey.NULL_CACHE_KEY;
1✔
1536
    }
1537
    return cacheKey;
1✔
1538
  }
1539

1540
  private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) {
1541
    if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) {
1✔
1542
      CacheKey combinedKey;
1543
      try {
1544
        combinedKey = rowKey.clone();
1✔
1545
      } catch (CloneNotSupportedException e) {
×
1546
        throw new ExecutorException("Error cloning cache key.  Cause: " + e, e);
×
1547
      }
1✔
1548
      combinedKey.update(parentRowKey);
1✔
1549
      return combinedKey;
1✔
1550
    }
1551
    return CacheKey.NULL_CACHE_KEY;
1✔
1552
  }
1553

1554
  private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {
1555
    List<ResultMapping> resultMappings = resultMap.getIdResultMappings();
1✔
1556
    if (resultMappings.isEmpty()) {
1✔
1557
      resultMappings = resultMap.getPropertyResultMappings();
1✔
1558
    }
1559
    return resultMappings;
1✔
1560
  }
1561

1562
  private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1563
      List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {
1564
    for (ResultMapping resultMapping : resultMappings) {
1✔
1565
      if (resultMapping.isSimple()) {
1✔
1566
        final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
1✔
1567
        TypeHandler<?> th = resultMapping.getTypeHandler();
1✔
1568
        Set<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
1✔
1569
        // Issue #114
1570
        if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
1!
1571
          if (th == null) {
1✔
1572
            th = typeHandlerRegistry.getTypeHandler(rsw.getJdbcType(column));
1✔
1573
          }
1574
          final Object value = th.getResult(rsw.getResultSet(), column);
1✔
1575
          if (value != null || configuration.isReturnInstanceForEmptyRow()) {
1✔
1576
            cacheKey.update(column);
1✔
1577
            cacheKey.update(value);
1✔
1578
          }
1579
        }
1580
      }
1581
    }
1✔
1582
  }
1✔
1583

1584
  private void createRowKeyForUnmappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1585
      String columnPrefix) throws SQLException {
1586
    final MetaClass metaType = MetaClass.forClass(resultMap.getType(), reflectorFactory);
1✔
1587
    List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
1✔
1588
    for (String column : unmappedColumnNames) {
1✔
1589
      String property = column;
1✔
1590
      if (columnPrefix != null && !columnPrefix.isEmpty()) {
1!
1591
        // When columnPrefix is specified, ignore columns without the prefix.
1592
        if (!column.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
1593
          continue;
1✔
1594
        }
1595
        property = column.substring(columnPrefix.length());
1✔
1596
      }
1597
      if (metaType.findProperty(property, configuration.isMapUnderscoreToCamelCase()) != null) {
1✔
1598
        String value = rsw.getResultSet().getString(column);
1✔
1599
        if (value != null) {
1✔
1600
          cacheKey.update(column);
1✔
1601
          cacheKey.update(value);
1✔
1602
        }
1603
      }
1604
    }
1✔
1605
  }
1✔
1606

1607
  private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException {
1608
    List<String> columnNames = rsw.getColumnNames();
×
1609
    for (String columnName : columnNames) {
×
1610
      final String value = rsw.getResultSet().getString(columnName);
×
1611
      if (value != null) {
×
1612
        cacheKey.update(columnName);
×
1613
        cacheKey.update(value);
×
1614
      }
1615
    }
×
1616
  }
×
1617

1618
  private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
1619
    linkObjects(metaObject, resultMapping, rowValue, false);
1✔
1620
  }
1✔
1621

1622
  private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue,
1623
      boolean isNestedCursorResult) {
1624
    final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1✔
1625
    if (collectionProperty != null) {
1✔
1626
      final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1✔
1627
      if (isNestedCursorResult) {
1!
1628
        targetMetaObject.addAll((List<?>) rowValue);
×
1629
      } else {
1630
        targetMetaObject.add(rowValue);
1✔
1631
      }
1632

1633
      // it is possible for pending creations to get set via property mappings,
1634
      // keep track of these, so we can rebuild them.
1635
      final Object originalObject = metaObject.getOriginalObject();
1✔
1636
      if (rowValue instanceof PendingConstructorCreation && !pendingPccRelations.containsKey(originalObject)) {
1✔
1637
        PendingRelation pendingRelation = new PendingRelation();
1✔
1638
        pendingRelation.propertyMapping = resultMapping;
1✔
1639
        pendingRelation.metaObject = metaObject;
1✔
1640

1641
        pendingPccRelations.put(originalObject, pendingRelation);
1✔
1642
      }
1643
    } else {
1✔
1644
      metaObject.setValue(resultMapping.getProperty(),
1✔
1645
          isNestedCursorResult ? toSingleObj((List<?>) rowValue) : rowValue);
1!
1646
    }
1647
  }
1✔
1648

1649
  private Object toSingleObj(List<?> list) {
1650
    // Even if there are multiple elements, silently returns the first one.
1651
    return list.isEmpty() ? null : list.get(0);
×
1652
  }
1653

1654
  private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
1655
    final String propertyName = resultMapping.getProperty();
1✔
1656
    Object propertyValue = metaObject.getValue(propertyName);
1✔
1657
    if (propertyValue == null) {
1✔
1658
      Class<?> type = resultMapping.getJavaType();
1✔
1659
      if (type == null) {
1✔
1660
        type = metaObject.getSetterType(propertyName);
1✔
1661
      }
1662
      try {
1663
        if (objectFactory.isCollection(type)) {
1✔
1664
          propertyValue = objectFactory.create(type);
1✔
1665
          metaObject.setValue(propertyName, propertyValue);
1✔
1666
          return propertyValue;
1✔
1667
        }
1668
      } catch (Exception e) {
×
1669
        throw new ExecutorException(
×
1670
            "Error instantiating collection property for result '" + resultMapping.getProperty() + "'.  Cause: " + e,
×
1671
            e);
1672
      }
1✔
1673
    } else if (objectFactory.isCollection(propertyValue.getClass())) {
1✔
1674
      return propertyValue;
1✔
1675
    }
1676
    return null;
1✔
1677
  }
1678

1679
  private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
1680
    if (rsw.getColumnNames().size() == 1) {
1✔
1681
      return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0)));
1✔
1682
    }
1683
    return typeHandlerRegistry.hasTypeHandler(resultType);
1✔
1684
  }
1685

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