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

mybatis / mybatis-3 / 2741

06 Mar 2025 05:36PM UTC coverage: 87.285% (+0.06%) from 87.226%
2741

Pull #3379

github

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

3834 of 4655 branches covered (82.36%)

627 of 695 new or added lines in 61 files covered. (90.22%)

14 existing lines in 4 files now uncovered.

9913 of 11357 relevant lines covered (87.29%)

0.87 hits per line

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

90.96
/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.ObjectTypeHandler;
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
  // temporary marking flag that indicate using constructor mapping (use field to reduce memory usage)
113
  private boolean useConstructorMappings;
114

115
  private static class PendingRelation {
116
    public MetaObject metaObject;
117
    public ResultMapping propertyMapping;
118
  }
119

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

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

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

148
  //
149
  // HANDLE OUTPUT PARAMETER
150
  //
151

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

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

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

214
    final List<Object> multipleResults = new ArrayList<>();
1✔
215

216
    int resultSetCount = 0;
1✔
217
    ResultSetWrapper rsw = getFirstResultSet(stmt);
1✔
218

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

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

245
    return collapseSingleResultList(multipleResults);
1✔
246
  }
247

248
  @Override
249
  public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException {
250
    ErrorContext.instance().activity("handling cursor results").object(mappedStatement.getId());
1✔
251

252
    ResultSetWrapper rsw = getFirstResultSet(stmt);
1✔
253

254
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
1✔
255

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

262
    ResultMap resultMap = resultMaps.get(0);
1✔
263
    return new DefaultCursor<>(this, resultMap, rsw, rowBounds);
1✔
264
  }
265

266
  private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
267
    ResultSet rs = null;
1✔
268
    SQLException e1 = null;
1✔
269

270
    try {
271
      rs = stmt.getResultSet();
1✔
272
    } catch (SQLException e) {
×
273
      // Oracle throws ORA-17283 for implicit cursor
274
      e1 = e;
×
275
    }
1✔
276

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

292
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
1✔
293
  }
294

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

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

318
  private void closeResultSet(ResultSet rs) {
319
    try {
320
      if (rs != null) {
1!
321
        rs.close();
1✔
322
      }
323
    } catch (SQLException e) {
×
324
      // ignore
325
    }
1✔
326
  }
1✔
327

328
  private void cleanUpAfterHandlingResultSet() {
329
    nestedResultObjects.clear();
1✔
330
  }
1✔
331

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

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

358
  @SuppressWarnings("unchecked")
359
  private List<Object> collapseSingleResultList(List<Object> multipleResults) {
360
    return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
1✔
361
  }
362

363
  //
364
  // HANDLE ROWS FOR SIMPLE RESULTMAP
365
  //
366

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

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

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

396
  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
397
      ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
398
    final boolean useCollectionConstructorInjection = resultMap.hasResultMapsUsingConstructorCollection();
1✔
399

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

413
        createAndStorePendingCreation(resultHandler, resultSet, resultContext, (PendingConstructorCreation) rowValue);
×
414
      }
415
    }
1✔
416
  }
1✔
417

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

425
    if (pendingPccRelations.containsKey(rowValue)) {
1✔
426
      createPendingConstructorCreations(rowValue);
1✔
427
    }
428

429
    callResultHandler(resultHandler, resultContext, rowValue);
1✔
430
  }
1✔
431

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

439
  private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
440
    return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
1✔
441
  }
442

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

457
  //
458
  // GET VALUE FROM ROW FOR SIMPLE RESULT MAP
459
  //
460

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

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

481
      if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
482
        nestedResultObjects.put(combinedKey, rowValue);
1✔
483
      }
484
    }
485

486
    return rowValue;
1✔
487
  }
488

489
  //
490
  // GET VALUE FROM ROW FOR NESTED RESULT MAP
491
  //
492

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

526
  private void putAncestor(Object resultObject, String resultMapId) {
527
    ancestorObjects.put(resultMapId, resultObject);
1✔
528
  }
1✔
529

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

541
  //
542
  // PROPERTY MAPPINGS
543
  //
544

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

582
  private Object getPropertyMappingValue(ResultSetWrapper rsw, MetaObject metaResultObject,
583
      ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
584
    final ResultSet rs = rsw.getResultSet();
1✔
585
    if (propertyMapping.getNestedQueryId() != null) {
1✔
586
      return getNestedQueryMappingValue(rsw, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
1✔
587
    }
588
    if (JdbcType.CURSOR.equals(propertyMapping.getJdbcType())) {
1!
NEW
589
      List<Object> results = getNestedCursorValue(rsw, propertyMapping, columnPrefix);
×
590
      linkObjects(metaResultObject, propertyMapping, results.get(0), true);
×
591
      return metaResultObject.getValue(propertyMapping.getProperty());
×
592
    }
593
    if (propertyMapping.getResultSet() != null) {
1✔
594
      addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
1✔
595
      return DEFERRED;
1✔
596
    } else {
597
      final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
1✔
598
      TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
1✔
599
      if (typeHandler == null) {
1✔
600
        final String property = propertyMapping.getProperty();
1✔
601
        final Type javaType = property == null ? null : metaResultObject.getGenericSetterType(property).getKey();
1✔
602
        typeHandler = rsw.getTypeHandler(javaType, column);
1✔
603
        if (typeHandler == null) {
1✔
604
          throw new ExecutorException(
1✔
605
              "No type handler found for '" + javaType + "' and JDBC type '" + rsw.getJdbcType(column) + "'");
1✔
606
        }
607
      }
608
      return typeHandler.getResult(rs, column);
1✔
609
    }
610
  }
611

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

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

671
  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
672
      String columnPrefix) throws SQLException {
673
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
1✔
674
    boolean foundValues = false;
1✔
675
    if (!autoMapping.isEmpty()) {
1✔
676
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
1✔
677
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
1✔
678
        if (value != null) {
1✔
679
          foundValues = true;
1✔
680
        }
681
        if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) {
1!
682
          // gcode issue #377, call setter on nulls (value is not 'found')
683
          metaObject.setValue(mapping.property, value);
1✔
684
        }
685
      }
1✔
686
    }
687
    return foundValues;
1✔
688
  }
689

690
  // MULTIPLE RESULT SETS
691

692
  private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException {
693
    CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(),
1✔
694
        parentMapping.getForeignColumn());
1✔
695
    List<PendingRelation> parents = pendingRelations.get(parentKey);
1✔
696
    if (parents != null) {
1✔
697
      for (PendingRelation parent : parents) {
1✔
698
        if (parent != null && rowValue != null) {
1!
699
          linkObjects(parent.metaObject, parent.propertyMapping, rowValue);
1✔
700
        }
701
      }
1✔
702
    }
703
  }
1✔
704

705
  private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, ResultMapping parentMapping)
706
      throws SQLException {
707
    CacheKey cacheKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(),
1✔
708
        parentMapping.getColumn());
1✔
709
    PendingRelation deferLoad = new PendingRelation();
1✔
710
    deferLoad.metaObject = metaResultObject;
1✔
711
    deferLoad.propertyMapping = parentMapping;
1✔
712
    List<PendingRelation> relations = pendingRelations.computeIfAbsent(cacheKey, k -> new ArrayList<>());
1✔
713
    // issue #255
714
    relations.add(deferLoad);
1✔
715
    ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet());
1✔
716
    if (previous == null) {
1✔
717
      nextResultMaps.put(parentMapping.getResultSet(), parentMapping);
1✔
718
    } else if (!previous.equals(parentMapping)) {
1!
719
      throw new ExecutorException("Two different properties are mapped to the same resultSet");
×
720
    }
721
  }
1✔
722

723
  private CacheKey createKeyForMultipleResults(ResultSet rs, ResultMapping resultMapping, String names, String columns)
724
      throws SQLException {
725
    CacheKey cacheKey = new CacheKey();
1✔
726
    cacheKey.update(resultMapping);
1✔
727
    if (columns != null && names != null) {
1!
728
      String[] columnsArray = columns.split(",");
1✔
729
      String[] namesArray = names.split(",");
1✔
730
      for (int i = 0; i < columnsArray.length; i++) {
1✔
731
        Object value = rs.getString(columnsArray[i]);
1✔
732
        if (value != null) {
1!
733
          cacheKey.update(namesArray[i]);
1✔
734
          cacheKey.update(value);
1✔
735
        }
736
      }
737
    }
738
    return cacheKey;
1✔
739
  }
740

741
  //
742
  // INSTANTIATION & CONSTRUCTOR MAPPING
743
  //
744

745
  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,
746
      String columnPrefix, CacheKey parentRowKey) throws SQLException {
747
    this.useConstructorMappings = false; // reset previous mapping result
1✔
748
    final List<Class<?>> constructorArgTypes = new ArrayList<>();
1✔
749
    final List<Object> constructorArgs = new ArrayList<>();
1✔
750

751
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix,
1✔
752
        parentRowKey);
753
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
1✔
754
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
1✔
755
      for (ResultMapping propertyMapping : propertyMappings) {
1✔
756
        // issue gcode #109 && issue #149
757
        if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
1✔
758
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration,
1✔
759
              objectFactory, constructorArgTypes, constructorArgs);
760
          break;
1✔
761
        }
762
      }
1✔
763

764
      // (issue #101)
765
      if (resultMap.hasResultMapsUsingConstructorCollection() && resultObject instanceof PendingConstructorCreation) {
1!
766
        linkNestedPendingCreations(rsw, resultMap, columnPrefix, parentRowKey,
1✔
767
            (PendingConstructorCreation) resultObject, constructorArgs);
768
      }
769
    }
770

771
    this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
1✔
772
    return resultObject;
1✔
773
  }
774

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

778
    final Class<?> resultType = resultMap.getType();
1✔
779
    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
1✔
780
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
1✔
781
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
1✔
782
      return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
1✔
783
    }
784
    if (!constructorMappings.isEmpty()) {
1✔
785
      return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs,
1✔
786
          columnPrefix, resultMap.hasResultMapsUsingConstructorCollection(), parentRowKey);
1✔
787
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
1✔
788
      return objectFactory.create(resultType);
1✔
789
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
1!
790
      return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes,
1✔
791
          constructorArgs);
792
    }
793
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
×
794
  }
795

796
  Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType,
797
      List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
798
      String columnPrefix, boolean useCollectionConstructorInjection, CacheKey parentRowKey) {
799
    boolean foundValues = false;
1✔
800

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

834
      constructorArgTypes.add(parameterType);
1✔
835
      constructorArgs.add(value);
1✔
836

837
      foundValues = value != null || foundValues;
1✔
838
    }
1✔
839

840
    if (!foundValues) {
1✔
841
      return null;
1✔
842
    }
843

844
    if (useCollectionConstructorInjection) {
1✔
845
      // at least one of the nestedResultMaps contained a collection, we have to defer until later
846
      return new PendingConstructorCreation(resultType, constructorArgTypes, constructorArgs);
1✔
847
    }
848

849
    return objectFactory.create(resultType, constructorArgTypes, constructorArgs);
1✔
850
  }
851

852
  private Object createByConstructorSignature(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
853
      Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
854
    return applyConstructorAutomapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs,
1✔
855
        findConstructorForAutomapping(resultType, rsw).orElseThrow(() -> new ExecutorException(
1✔
856
            "No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames())));
×
857
  }
858

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

882
  private boolean findUsableConstructorByArgTypes(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) {
883
    final Class<?>[] parameterTypes = constructor.getParameterTypes();
1✔
884
    if (parameterTypes.length != jdbcTypes.size()) {
1✔
885
      return false;
1✔
886
    }
887
    for (int i = 0; i < parameterTypes.length; i++) {
1✔
888
      if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) {
1!
889
        return false;
×
890
      }
891
    }
892
    return true;
1✔
893
  }
894

895
  private Object applyConstructorAutomapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
896
      Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor)
897
      throws SQLException {
898
    boolean foundValues = false;
1✔
899
    if (configuration.isArgNameBasedConstructorAutoMapping()) {
1✔
900
      foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, constructorArgTypes,
1✔
901
          constructorArgs, constructor, foundValues);
902
    } else {
903
      foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor,
1✔
904
          foundValues);
905
    }
906
    return foundValues || configuration.isReturnInstanceForEmptyRow()
1!
907
        ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
1✔
908
  }
909

910
  private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,
911
      List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {
912
    Class<?>[] parameterTypes = constructor.getParameterTypes();
1✔
913

914
    if (parameterTypes.length > rsw.getClassNames().size()) {
1✔
915
      throw new ExecutorException(MessageFormat.format(
1✔
916
          "Constructor auto-mapping of ''{0}'' failed. The constructor takes ''{1}'' arguments, but there are only ''{2}'' columns in the result set.",
917
          constructor, parameterTypes.length, rsw.getClassNames().size()));
1✔
918
    }
919

920
    for (int i = 0; i < parameterTypes.length; i++) {
1✔
921
      Class<?> parameterType = parameterTypes[i];
1✔
922
      String columnName = rsw.getColumnNames().get(i);
1✔
923
      TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
1✔
924
      Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
925
      constructorArgTypes.add(parameterType);
1✔
926
      constructorArgs.add(value);
1✔
927
      foundValues = value != null || foundValues;
1!
928
    }
929
    return foundValues;
1✔
930
  }
931

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

972
  private boolean columnMatchesParam(String columnName, String paramName, String columnPrefix) {
973
    if (columnPrefix != null) {
1✔
974
      if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
975
        return false;
1✔
976
      }
977
      columnName = columnName.substring(columnPrefix.length());
1✔
978
    }
979
    return paramName
1✔
980
        .equalsIgnoreCase(configuration.isMapUnderscoreToCamelCase() ? columnName.replace("_", "") : columnName);
1✔
981
  }
982

983
  private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
984
      throws SQLException {
985
    final Class<?> resultType = resultMap.getType();
1✔
986
    final String columnName;
987
    if (!resultMap.getResultMappings().isEmpty()) {
1✔
988
      final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
1✔
989
      final ResultMapping mapping = resultMappingList.get(0);
1✔
990
      columnName = prependPrefix(mapping.getColumn(), columnPrefix);
1✔
991
    } else {
1✔
992
      columnName = rsw.getColumnNames().get(0);
1✔
993
    }
994
    final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
1✔
995
    return typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
996
  }
997

998
  //
999
  // NESTED QUERY
1000
  //
1001

1002
  private Object getNestedQueryConstructorValue(ResultSetWrapper rsw, ResultMapping constructorMapping,
1003
      String columnPrefix) throws SQLException {
1004
    final String nestedQueryId = constructorMapping.getNestedQueryId();
1✔
1005
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
1✔
1006
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
1✔
1007
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rsw, constructorMapping,
1✔
1008
        nestedQueryParameterType, columnPrefix);
1009
    Object value = null;
1✔
1010
    if (nestedQueryParameterObject != null) {
1!
1011
      final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
1✔
1012
      final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
1✔
1013
          nestedBoundSql);
1014
      final Class<?> targetType = constructorMapping.getJavaType();
1✔
1015
      final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
1✔
1016
          nestedQueryParameterObject, targetType, key, nestedBoundSql);
1017
      value = resultLoader.loadResult();
1✔
1018
    }
1019
    return value;
1✔
1020
  }
1021

1022
  private Object getNestedQueryMappingValue(ResultSetWrapper rsw, MetaObject metaResultObject,
1023
      ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
1024
    final String nestedQueryId = propertyMapping.getNestedQueryId();
1✔
1025
    final String property = propertyMapping.getProperty();
1✔
1026
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
1✔
1027
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
1✔
1028
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rsw, propertyMapping,
1✔
1029
        nestedQueryParameterType, columnPrefix);
1030
    Object value = null;
1✔
1031
    if (nestedQueryParameterObject != null) {
1✔
1032
      final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
1✔
1033
      final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
1✔
1034
          nestedBoundSql);
1035
      final Class<?> targetType = propertyMapping.getJavaType();
1✔
1036
      if (executor.isCached(nestedQuery, key)) {
1✔
1037
        executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
1✔
1038
        value = DEFERRED;
1✔
1039
      } else {
1040
        final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
1✔
1041
            nestedQueryParameterObject, targetType, key, nestedBoundSql);
1042
        if (propertyMapping.isLazy()) {
1✔
1043
          lazyLoader.addLoader(property, metaResultObject, resultLoader);
1✔
1044
          value = DEFERRED;
1✔
1045
        } else {
1046
          value = resultLoader.loadResult();
1✔
1047
        }
1048
      }
1049
    }
1050
    return value;
1✔
1051
  }
1052

1053
  private Object prepareParameterForNestedQuery(ResultSetWrapper rsw, ResultMapping resultMapping,
1054
      Class<?> parameterType, String columnPrefix) throws SQLException {
1055
    if (resultMapping.isCompositeResult()) {
1✔
1056
      return prepareCompositeKeyParameter(rsw, resultMapping, parameterType, columnPrefix);
1✔
1057
    }
1058
    return prepareSimpleKeyParameter(rsw, resultMapping, parameterType, columnPrefix);
1✔
1059
  }
1060

1061
  private Object prepareSimpleKeyParameter(ResultSetWrapper rsw, ResultMapping resultMapping, Class<?> parameterType,
1062
      String columnPrefix) throws SQLException {
1063
    // parameterType is ignored in this case
1064
    final String columnName = prependPrefix(resultMapping.getColumn(), columnPrefix);
1✔
1065
    final TypeHandler<?> typeHandler = rsw.getTypeHandler(null, columnName);
1✔
1066
    return typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
1067
  }
1068

1069
  private Object prepareCompositeKeyParameter(ResultSetWrapper rsw, ResultMapping resultMapping, Class<?> parameterType,
1070
      String columnPrefix) throws SQLException {
1071
    // Map is used if parameterType is not specified
1072
    final Object parameterObject = instantiateParameterObject(parameterType);
1✔
1073
    final MetaObject metaObject = configuration.newMetaObject(parameterObject);
1✔
1074
    boolean foundValues = false;
1✔
1075
    for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
1✔
1076
      final String columnName = prependPrefix(innerResultMapping.getColumn(), columnPrefix);
1✔
1077
      final TypeHandler<?> typeHandler = rsw
1✔
1078
          .getTypeHandler(metaObject.getGenericSetterType(innerResultMapping.getProperty()).getKey(), columnName);
1✔
1079
      final Object propValue = typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
1080
      // issue #353 & #560 do not execute nested query if key is null
1081
      if (propValue != null) {
1✔
1082
        metaObject.setValue(innerResultMapping.getProperty(), propValue);
1✔
1083
        foundValues = true;
1✔
1084
      }
1085
    }
1✔
1086
    return foundValues ? parameterObject : null;
1✔
1087
  }
1088

1089
  private Object instantiateParameterObject(Class<?> parameterType) {
1090
    if (parameterType == null) {
1✔
1091
      return new HashMap<>();
1✔
1092
    }
1093
    if (ParamMap.class.equals(parameterType)) {
1✔
1094
      return new HashMap<>(); // issue #649
1✔
1095
    } else {
1096
      return objectFactory.create(parameterType);
1✔
1097
    }
1098
  }
1099

1100
  //
1101
  // DISCRIMINATOR
1102
  //
1103

1104
  public ResultMap resolveDiscriminatedResultMap(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
1105
      throws SQLException {
1106
    Set<String> pastDiscriminators = new HashSet<>();
1✔
1107
    Discriminator discriminator = resultMap.getDiscriminator();
1✔
1108
    while (discriminator != null) {
1✔
1109
      final Object value = getDiscriminatorValue(rsw, discriminator, columnPrefix);
1✔
1110
      final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
1✔
1111
      if (!configuration.hasResultMap(discriminatedMapId)) {
1✔
1112
        break;
1✔
1113
      }
1114
      resultMap = configuration.getResultMap(discriminatedMapId);
1✔
1115
      Discriminator lastDiscriminator = discriminator;
1✔
1116
      discriminator = resultMap.getDiscriminator();
1✔
1117
      if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
1!
1118
        break;
1✔
1119
      }
1120
    }
1✔
1121
    return resultMap;
1✔
1122
  }
1123

1124
  private Object getDiscriminatorValue(ResultSetWrapper rsw, Discriminator discriminator, String columnPrefix)
1125
      throws SQLException {
1126
    final ResultMapping resultMapping = discriminator.getResultMapping();
1✔
1127
    String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
1✔
1128
    TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
1✔
1129
    if (typeHandler == null) {
1✔
1130
      typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.getJavaType(), rsw.getJdbcType(column));
1✔
1131
    }
1132
    return typeHandler.getResult(rsw.getResultSet(), column);
1✔
1133
  }
1134

1135
  private String prependPrefix(String columnName, String prefix) {
1136
    if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
1!
1137
      return columnName;
1✔
1138
    }
1139
    return prefix + columnName;
1✔
1140
  }
1141

1142
  //
1143
  // HANDLE NESTED RESULT MAPS
1144
  //
1145

1146
  private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap,
1147
      ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
1148
    final boolean useCollectionConstructorInjection = resultMap.hasResultMapsUsingConstructorCollection();
1✔
1149
    PendingConstructorCreation lastHandledCreation = null;
1✔
1150
    if (useCollectionConstructorInjection) {
1✔
1151
      verifyPendingCreationPreconditions(parentMapping);
1✔
1152
    }
1153

1154
    final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
1✔
1155
    ResultSet resultSet = rsw.getResultSet();
1✔
1156
    skipRows(resultSet, rowBounds);
1✔
1157
    Object rowValue = previousRowValue;
1✔
1158

1159
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
1!
1160
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw, resultMap, null);
1✔
1161
      final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
1✔
1162

1163
      final Object partialObject = nestedResultObjects.get(rowKey);
1✔
1164
      final boolean foundNewUniqueRow = partialObject == null;
1✔
1165

1166
      // issue #577, #542 && #101
1167
      if (useCollectionConstructorInjection) {
1✔
1168
        if (foundNewUniqueRow && lastHandledCreation != null) {
1✔
1169
          createAndStorePendingCreation(resultHandler, resultSet, resultContext, lastHandledCreation);
1✔
1170
          lastHandledCreation = null;
1✔
1171
        }
1172

1173
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1174
        if (rowValue instanceof PendingConstructorCreation) {
1!
1175
          lastHandledCreation = (PendingConstructorCreation) rowValue;
1✔
1176
        }
1177
      } else if (mappedStatement.isResultOrdered()) {
1✔
1178
        if (foundNewUniqueRow && rowValue != null) {
1✔
1179
          nestedResultObjects.clear();
1✔
1180
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1181
        }
1182
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1183
      } else {
1184
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1185
        if (foundNewUniqueRow) {
1✔
1186
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1187
        }
1188
      }
1189
    }
1✔
1190

1191
    if (useCollectionConstructorInjection && lastHandledCreation != null) {
1!
1192
      createAndStorePendingCreation(resultHandler, resultSet, resultContext, lastHandledCreation);
1✔
1193
    } else if (rowValue != null && mappedStatement.isResultOrdered()
1✔
1194
        && shouldProcessMoreRows(resultContext, rowBounds)) {
1✔
1195
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1196
      previousRowValue = null;
1✔
1197
    } else if (rowValue != null) {
1✔
1198
      previousRowValue = rowValue;
1✔
1199
    }
1200
  }
1✔
1201

1202
  //
1203
  // NESTED RESULT MAP (PENDING CONSTRUCTOR CREATIONS)
1204
  //
1205
  private void linkNestedPendingCreations(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
1206
      CacheKey parentRowKey, PendingConstructorCreation pendingCreation, List<Object> constructorArgs)
1207
      throws SQLException {
1208
    if (parentRowKey == null) {
1!
1209
      // nothing to link, possibly due to simple (non-nested) result map
1210
      return;
×
1211
    }
1212

1213
    final CacheKey rowKey = createRowKey(resultMap, rsw, columnPrefix);
1✔
1214
    final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
1215

1216
    if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1!
1217
      nestedResultObjects.put(combinedKey, pendingCreation);
1✔
1218
    }
1219

1220
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
1✔
1221
    for (int index = 0; index < constructorMappings.size(); index++) {
1✔
1222
      final ResultMapping constructorMapping = constructorMappings.get(index);
1✔
1223
      final String nestedResultMapId = constructorMapping.getNestedResultMapId();
1✔
1224

1225
      if (nestedResultMapId == null) {
1✔
1226
        continue;
1✔
1227
      }
1228

1229
      final Class<?> javaType = constructorMapping.getJavaType();
1✔
1230
      if (javaType == null || !objectFactory.isCollection(javaType)) {
1!
1231
        continue;
1✔
1232
      }
1233

1234
      final String constructorColumnPrefix = getColumnPrefix(columnPrefix, constructorMapping);
1✔
1235
      final ResultMap nestedResultMap = resolveDiscriminatedResultMap(rsw,
1✔
1236
          configuration.getResultMap(constructorMapping.getNestedResultMapId()), constructorColumnPrefix);
1✔
1237

1238
      final Object actualValue = constructorArgs.get(index);
1✔
1239
      final boolean hasValue = actualValue != null;
1✔
1240
      final boolean isInnerCreation = actualValue instanceof PendingConstructorCreation;
1✔
1241
      final boolean alreadyCreatedCollection = hasValue && objectFactory.isCollection(actualValue.getClass());
1!
1242

1243
      if (!isInnerCreation) {
1✔
1244
        final Collection<Object> value = pendingCreation.initializeCollectionForResultMapping(objectFactory,
1✔
1245
            nestedResultMap, constructorMapping, index);
1✔
1246
        if (!alreadyCreatedCollection) {
1!
1247
          // override values with empty collection
1248
          constructorArgs.set(index, value);
1✔
1249
        }
1250

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

1255
        if (nestedCombinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
1256
          nestedResultObjects.put(nestedCombinedKey, pendingCreation);
1✔
1257
        }
1258

1259
        if (hasValue) {
1✔
1260
          pendingCreation.linkCollectionValue(constructorMapping, actualValue);
1✔
1261
        }
1262
      } else {
1✔
1263
        final PendingConstructorCreation innerCreation = (PendingConstructorCreation) actualValue;
1✔
1264
        final Collection<Object> value = pendingCreation.initializeCollectionForResultMapping(objectFactory,
1✔
1265
            nestedResultMap, constructorMapping, index);
1✔
1266
        // we will fill this collection when building the final object
1267
        constructorArgs.set(index, value);
1✔
1268
        // link the creation for building later
1269
        pendingCreation.linkCreation(constructorMapping, innerCreation);
1✔
1270
      }
1271
    }
1272
  }
1✔
1273

1274
  private boolean applyNestedPendingConstructorCreations(ResultSetWrapper rsw, ResultMap resultMap,
1275
      MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject, boolean foundValues) {
1276
    if (newObject) {
1✔
1277
      // new objects are linked by createResultObject
1278
      return false;
1✔
1279
    }
1280

1281
    for (ResultMapping constructorMapping : resultMap.getConstructorResultMappings()) {
1✔
1282
      final String nestedResultMapId = constructorMapping.getNestedResultMapId();
1✔
1283
      final Class<?> parameterType = constructorMapping.getJavaType();
1✔
1284
      if (nestedResultMapId == null || constructorMapping.getResultSet() != null || parameterType == null
1!
1285
          || !objectFactory.isCollection(parameterType)) {
1✔
1286
        continue;
1✔
1287
      }
1288

1289
      try {
1290
        final String columnPrefix = getColumnPrefix(parentPrefix, constructorMapping);
1✔
1291
        final ResultMap nestedResultMap = getNestedResultMap(rsw, nestedResultMapId, columnPrefix);
1✔
1292

1293
        final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
1✔
1294
        final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
1295

1296
        // should have inserted already as a nested result object
1297
        Object rowValue = nestedResultObjects.get(combinedKey);
1✔
1298

1299
        PendingConstructorCreation pendingConstructorCreation = null;
1✔
1300
        if (rowValue instanceof PendingConstructorCreation) {
1✔
1301
          pendingConstructorCreation = (PendingConstructorCreation) rowValue;
1✔
1302
        } else if (rowValue != null) {
1✔
1303
          // found a simple object that was already linked/handled
1304
          continue;
1✔
1305
        }
1306

1307
        final boolean newValueForNestedResultMap = pendingConstructorCreation == null;
1✔
1308
        if (newValueForNestedResultMap) {
1✔
1309
          final Object parentObject = metaObject.getOriginalObject();
1✔
1310
          if (!(parentObject instanceof PendingConstructorCreation)) {
1!
1311
            throw new ExecutorException(
×
1312
                "parentObject is not a pending creation, cannot continue linking! MyBatis internal error!");
1313
          }
1314

1315
          pendingConstructorCreation = (PendingConstructorCreation) parentObject;
1✔
1316
        }
1317

1318
        rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix,
1✔
1319
            newValueForNestedResultMap ? null : pendingConstructorCreation);
1✔
1320

1321
        if (rowValue == null) {
1✔
1322
          continue;
1✔
1323
        }
1324

1325
        if (rowValue instanceof PendingConstructorCreation) {
1✔
1326
          if (newValueForNestedResultMap) {
1✔
1327
            // we created a brand new pcc. this is a new collection value
1328
            pendingConstructorCreation.linkCreation(constructorMapping, (PendingConstructorCreation) rowValue);
1✔
1329
            foundValues = true;
1✔
1330
          }
1331
        } else {
1332
          pendingConstructorCreation.linkCollectionValue(constructorMapping, rowValue);
1✔
1333
          foundValues = true;
1✔
1334

1335
          if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
1336
            nestedResultObjects.put(combinedKey, pendingConstructorCreation);
1✔
1337
          }
1338
        }
1339
      } catch (SQLException e) {
×
1340
        throw new ExecutorException("Error getting constructor collection nested result map values for '"
×
1341
            + constructorMapping.getProperty() + "'.  Cause: " + e, e);
×
1342
      }
1✔
1343
    }
1✔
1344

1345
    return foundValues;
1✔
1346
  }
1347

1348
  private void createPendingConstructorCreations(Object rowValue) {
1349
    // handle possible pending creations within this object
1350
    // by now, the property mapping has been completely built, we can reconstruct it
1351
    final PendingRelation pendingRelation = pendingPccRelations.remove(rowValue);
1✔
1352
    final MetaObject metaObject = pendingRelation.metaObject;
1✔
1353
    final ResultMapping resultMapping = pendingRelation.propertyMapping;
1✔
1354

1355
    // get the list to be built
1356
    Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1✔
1357
    if (collectionProperty != null) {
1!
1358
      // we expect pending creations now
1359
      final Collection<Object> pendingCreations = (Collection<Object>) collectionProperty;
1✔
1360

1361
      // remove the link to the old collection
1362
      metaObject.setValue(resultMapping.getProperty(), null);
1✔
1363

1364
      // create new collection property
1365
      collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1✔
1366
      final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1✔
1367

1368
      // create the pending objects
1369
      for (Object pendingCreation : pendingCreations) {
1✔
1370
        if (pendingCreation instanceof PendingConstructorCreation) {
1!
1371
          final PendingConstructorCreation pendingConstructorCreation = (PendingConstructorCreation) pendingCreation;
1✔
1372
          targetMetaObject.add(pendingConstructorCreation.create(objectFactory));
1✔
1373
        }
1374
      }
1✔
1375
    }
1376
  }
1✔
1377

1378
  private void verifyPendingCreationPreconditions(ResultMapping parentMapping) {
1379
    if (parentMapping != null) {
1!
1380
      throw new ExecutorException(
×
1381
          "Cannot construct objects with collections in constructors using multiple result sets yet!");
1382
    }
1383

1384
    if (!mappedStatement.isResultOrdered()) {
1!
1385
      throw new ExecutorException("Cannot reliably construct result if we are not sure the results are ordered "
×
1386
          + "so that no new previous rows would occur, set resultOrdered on your mapped statement if you have verified this");
1387
    }
1388
  }
1✔
1389

1390
  private void createAndStorePendingCreation(ResultHandler<?> resultHandler, ResultSet resultSet,
1391
      DefaultResultContext<Object> resultContext, PendingConstructorCreation pendingCreation) throws SQLException {
1392
    final Object result = pendingCreation.create(objectFactory);
1✔
1393
    storeObject(resultHandler, resultContext, result, null, resultSet);
1✔
1394
    nestedResultObjects.clear();
1✔
1395
  }
1✔
1396

1397
  //
1398
  // NESTED RESULT MAP (JOIN MAPPING)
1399
  //
1400

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

1440
    // (issue #101)
1441
    if (resultMap.hasResultMapsUsingConstructorCollection()) {
1✔
1442
      foundValues = applyNestedPendingConstructorCreations(rsw, resultMap, metaObject, parentPrefix, parentRowKey,
1✔
1443
          newObject, foundValues);
1444
    }
1445

1446
    return foundValues;
1✔
1447
  }
1448

1449
  private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) {
1450
    final StringBuilder columnPrefixBuilder = new StringBuilder();
1✔
1451
    if (parentPrefix != null) {
1✔
1452
      columnPrefixBuilder.append(parentPrefix);
1✔
1453
    }
1454
    if (resultMapping.getColumnPrefix() != null) {
1✔
1455
      columnPrefixBuilder.append(resultMapping.getColumnPrefix());
1✔
1456
    }
1457
    return columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH);
1✔
1458
  }
1459

1460
  private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSetWrapper rsw)
1461
      throws SQLException {
1462
    Set<String> notNullColumns = resultMapping.getNotNullColumns();
1✔
1463
    if (notNullColumns != null && !notNullColumns.isEmpty()) {
1✔
1464
      ResultSet rs = rsw.getResultSet();
1✔
1465
      for (String column : notNullColumns) {
1✔
1466
        rs.getObject(prependPrefix(column, columnPrefix));
1✔
1467
        if (!rs.wasNull()) {
1✔
1468
          return true;
1✔
1469
        }
1470
      }
1✔
1471
      return false;
1✔
1472
    }
1473
    if (columnPrefix != null) {
1✔
1474
      for (String columnName : rsw.getColumnNames()) {
1✔
1475
        if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix.toUpperCase(Locale.ENGLISH))) {
1✔
1476
          return true;
1✔
1477
        }
1478
      }
1✔
1479
      return false;
1✔
1480
    }
1481
    return true;
1✔
1482
  }
1483

1484
  private ResultMap getNestedResultMap(ResultSetWrapper rsw, String nestedResultMapId, String columnPrefix)
1485
      throws SQLException {
1486
    ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId);
1✔
1487
    return resolveDiscriminatedResultMap(rsw, nestedResultMap, columnPrefix);
1✔
1488
  }
1489

1490
  //
1491
  // UNIQUE RESULT KEY
1492
  //
1493

1494
  private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
1495
    final CacheKey cacheKey = new CacheKey();
1✔
1496
    cacheKey.update(resultMap.getId());
1✔
1497
    List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
1✔
1498
    if (resultMappings.isEmpty()) {
1✔
1499
      if (Map.class.isAssignableFrom(resultMap.getType())) {
1!
1500
        createRowKeyForMap(rsw, cacheKey);
×
1501
      } else {
1502
        createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
1✔
1503
      }
1504
    } else {
1505
      createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
1✔
1506
    }
1507
    if (cacheKey.getUpdateCount() < 2) {
1✔
1508
      return CacheKey.NULL_CACHE_KEY;
1✔
1509
    }
1510
    return cacheKey;
1✔
1511
  }
1512

1513
  private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) {
1514
    if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) {
1✔
1515
      CacheKey combinedKey;
1516
      try {
1517
        combinedKey = rowKey.clone();
1✔
1518
      } catch (CloneNotSupportedException e) {
×
1519
        throw new ExecutorException("Error cloning cache key.  Cause: " + e, e);
×
1520
      }
1✔
1521
      combinedKey.update(parentRowKey);
1✔
1522
      return combinedKey;
1✔
1523
    }
1524
    return CacheKey.NULL_CACHE_KEY;
1✔
1525
  }
1526

1527
  private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {
1528
    List<ResultMapping> resultMappings = resultMap.getIdResultMappings();
1✔
1529
    if (resultMappings.isEmpty()) {
1✔
1530
      resultMappings = resultMap.getPropertyResultMappings();
1✔
1531
    }
1532
    return resultMappings;
1✔
1533
  }
1534

1535
  private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1536
      List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {
1537
    for (ResultMapping resultMapping : resultMappings) {
1✔
1538
      if (resultMapping.isSimple()) {
1✔
1539
        final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
1✔
1540
        TypeHandler<?> th = resultMapping.getTypeHandler();
1✔
1541
        Set<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
1✔
1542
        // Issue #114
1543
        if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
1!
1544
          if (th == null) {
1✔
1545
            th = typeHandlerRegistry.getTypeHandler(rsw.getJdbcType(column));
1✔
1546
          }
1547
          if (th == null) {
1✔
1548
            th = ObjectTypeHandler.INSTANCE;
1✔
1549
          }
1550
          final Object value = th.getResult(rsw.getResultSet(), column);
1✔
1551
          if (value != null || configuration.isReturnInstanceForEmptyRow()) {
1✔
1552
            cacheKey.update(column);
1✔
1553
            cacheKey.update(value);
1✔
1554
          }
1555
        }
1556
      }
1557
    }
1✔
1558
  }
1✔
1559

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

1583
  private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException {
1584
    List<String> columnNames = rsw.getColumnNames();
×
1585
    for (String columnName : columnNames) {
×
1586
      final String value = rsw.getResultSet().getString(columnName);
×
1587
      if (value != null) {
×
1588
        cacheKey.update(columnName);
×
1589
        cacheKey.update(value);
×
1590
      }
1591
    }
×
1592
  }
×
1593

1594
  private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
1595
    linkObjects(metaObject, resultMapping, rowValue, false);
1✔
1596
  }
1✔
1597

1598
  private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue,
1599
      boolean isNestedCursorResult) {
1600
    final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1✔
1601
    if (collectionProperty != null) {
1✔
1602
      final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1✔
1603
      if (isNestedCursorResult) {
1!
1604
        targetMetaObject.addAll((List<?>) rowValue);
×
1605
      } else {
1606
        targetMetaObject.add(rowValue);
1✔
1607
      }
1608

1609
      // it is possible for pending creations to get set via property mappings,
1610
      // keep track of these, so we can rebuild them.
1611
      final Object originalObject = metaObject.getOriginalObject();
1✔
1612
      if (rowValue instanceof PendingConstructorCreation && !pendingPccRelations.containsKey(originalObject)) {
1✔
1613
        PendingRelation pendingRelation = new PendingRelation();
1✔
1614
        pendingRelation.propertyMapping = resultMapping;
1✔
1615
        pendingRelation.metaObject = metaObject;
1✔
1616

1617
        pendingPccRelations.put(originalObject, pendingRelation);
1✔
1618
      }
1619
    } else {
1✔
1620
      metaObject.setValue(resultMapping.getProperty(),
1✔
1621
          isNestedCursorResult ? toSingleObj((List<?>) rowValue) : rowValue);
1!
1622
    }
1623
  }
1✔
1624

1625
  private Object toSingleObj(List<?> list) {
1626
    // Even if there are multiple elements, silently returns the first one.
1627
    return list.isEmpty() ? null : list.get(0);
×
1628
  }
1629

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

1655
  private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
1656
    if (rsw.getColumnNames().size() == 1) {
1✔
1657
      return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0)));
1✔
1658
    }
1659
    return typeHandlerRegistry.hasTypeHandler(resultType);
1✔
1660
  }
1661

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