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

mybatis / mybatis-3 / #3633

24 May 2024 08:58AM CUT coverage: 87.257% (+0.09%) from 87.169%
#3633

Pull #3108

github

web-flow
Merge 7d3668285 into 043270b83
Pull Request #3108: #101: Add support for collection constructor creation

3644 of 4415 branches covered (82.54%)

209 of 227 new or added lines in 7 files covered. (92.07%)

3 existing lines in 1 file now uncovered.

9586 of 10986 relevant lines covered (87.26%)

0.87 hits per line

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

93.38
/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java
1
/*
2
 *    Copyright 2009-2024 the original author or authors.
3
 *
4
 *    Licensed under the Apache License, Version 2.0 (the "License");
5
 *    you may not use this file except in compliance with the License.
6
 *    You may obtain a copy of the License at
7
 *
8
 *       https://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *    Unless required by applicable law or agreed to in writing, software
11
 *    distributed under the License is distributed on an "AS IS" BASIS,
12
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *    See the License for the specific language governing permissions and
14
 *    limitations under the License.
15
 */
16
package org.apache.ibatis.executor.resultset;
17

18
import java.lang.reflect.Constructor;
19
import java.lang.reflect.Parameter;
20
import java.sql.CallableStatement;
21
import java.sql.ResultSet;
22
import java.sql.SQLException;
23
import java.sql.Statement;
24
import java.text.MessageFormat;
25
import java.util.ArrayList;
26
import java.util.Arrays;
27
import java.util.Collection;
28
import java.util.HashMap;
29
import java.util.HashSet;
30
import java.util.List;
31
import java.util.Locale;
32
import java.util.Map;
33
import java.util.Optional;
34
import java.util.Set;
35

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

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

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

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

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

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

103
  // Cached Automappings
104
  private final Map<String, List<UnMappedColumnAutoMapping>> autoMappingsCache = new HashMap<>();
1✔
105
  private final Map<String, List<String>> constructorAutoMappingColumns = new HashMap<>();
1✔
106

107
  // temporary marking flag that indicate using constructor mapping (use field to reduce memory usage)
108
  private boolean useConstructorMappings;
109

110
  private static class PendingRelation {
111
    public MetaObject metaObject;
112
    public ResultMapping propertyMapping;
113
  }
114

115
  private static class UnMappedColumnAutoMapping {
116
    private final String column;
117
    private final String property;
118
    private final TypeHandler<?> typeHandler;
119
    private final boolean primitive;
120

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

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

143
  //
144
  // HANDLE OUTPUT PARAMETER
145
  //
146

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

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

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

194
    final List<Object> multipleResults = new ArrayList<>();
1✔
195

196
    int resultSetCount = 0;
1✔
197
    ResultSetWrapper rsw = getFirstResultSet(stmt);
1✔
198

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

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

225
    return collapseSingleResultList(multipleResults);
1✔
226
  }
227

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

232
    ResultSetWrapper rsw = getFirstResultSet(stmt);
1✔
233

234
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
1✔
235

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

242
    ResultMap resultMap = resultMaps.get(0);
1✔
243
    return new DefaultCursor<>(this, resultMap, rsw, rowBounds);
1✔
244
  }
245

246
  private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
247
    ResultSet rs = stmt.getResultSet();
1✔
248
    while (rs == null) {
1✔
249
      // move forward to get the first resultset in case the driver
250
      // doesn't return the resultset as the first result (HSQLDB)
251
      if (stmt.getMoreResults()) {
1!
252
        rs = stmt.getResultSet();
×
253
      } else if (stmt.getUpdateCount() == -1) {
1!
254
        // no more results. Must be no resultset
255
        break;
1✔
256
      }
257
    }
258
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
1✔
259
  }
260

261
  private ResultSetWrapper getNextResultSet(Statement stmt) {
262
    // Making this method tolerant of bad JDBC drivers
263
    try {
264
      if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
1✔
265
        // Crazy Standard JDBC way of determining if there are more results
266
        // DO NOT try to 'improve' the condition even if IDE tells you to!
267
        // It's important that getUpdateCount() is called here.
268
        if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
1!
269
          ResultSet rs = stmt.getResultSet();
1✔
270
          if (rs == null) {
1!
271
            return getNextResultSet(stmt);
×
272
          } else {
273
            return new ResultSetWrapper(rs, configuration);
1✔
274
          }
275
        }
276
      }
277
    } catch (Exception e) {
×
278
      // Intentionally ignored.
279
    }
1✔
280
    return null;
1✔
281
  }
282

283
  private void closeResultSet(ResultSet rs) {
284
    try {
285
      if (rs != null) {
1!
286
        rs.close();
1✔
287
      }
288
    } catch (SQLException e) {
×
289
      // ignore
290
    }
1✔
291
  }
1✔
292

293
  private void cleanUpAfterHandlingResultSet() {
294
    nestedResultObjects.clear();
1✔
295
  }
1✔
296

297
  private void validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount) {
298
    if (rsw != null && resultMapCount < 1) {
1✔
299
      throw new ExecutorException(
1✔
300
          "A query was run and no Result Maps were found for the Mapped Statement '" + mappedStatement.getId()
1✔
301
              + "'. 'resultType' or 'resultMap' must be specified when there is no corresponding method.");
302
    }
303
  }
1✔
304

305
  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults,
306
      ResultMapping parentMapping) throws SQLException {
307
    try {
308
      if (parentMapping != null) {
1✔
309
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
1✔
310
      } else if (resultHandler == null) {
1✔
311
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
1✔
312
        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
1✔
313
        multipleResults.add(defaultResultHandler.getResultList());
1✔
314
      } else {
1✔
315
        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
1✔
316
      }
317
    } finally {
318
      // issue #228 (close resultsets)
319
      closeResultSet(rsw.getResultSet());
1✔
320
    }
321
  }
1✔
322

323
  @SuppressWarnings("unchecked")
324
  private List<Object> collapseSingleResultList(List<Object> multipleResults) {
325
    return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
1✔
326
  }
327

328
  //
329
  // HANDLE ROWS FOR SIMPLE RESULTMAP
330
  //
331

332
  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
333
      RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
334
    if (resultMap.hasNestedResultMaps()) {
1✔
335
      ensureNoRowBounds();
1✔
336
      checkResultHandler();
1✔
337
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
1✔
338
    } else {
339
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
1✔
340
    }
341
  }
1✔
342

343
  private void ensureNoRowBounds() {
344
    if (configuration.isSafeRowBoundsEnabled() && rowBounds != null
1!
345
        && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) {
×
346
      throw new ExecutorException(
×
347
          "Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. "
348
              + "Use safeRowBoundsEnabled=false setting to bypass this check.");
349
    }
350
  }
1✔
351

352
  protected void checkResultHandler() {
353
    if (resultHandler != null && configuration.isSafeResultHandlerEnabled() && !mappedStatement.isResultOrdered()) {
1!
354
      throw new ExecutorException(
1✔
355
          "Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler. "
356
              + "Use safeResultHandlerEnabled=false setting to bypass this check "
357
              + "or ensure your statement returns ordered data and set resultOrdered=true on it.");
358
    }
359
  }
1✔
360

361
  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
362
      ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
363
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
1✔
364
    ResultSet resultSet = rsw.getResultSet();
1✔
365
    skipRows(resultSet, rowBounds);
1✔
366
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
1!
367
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
1✔
368
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null, null);
1✔
369
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
370
    }
1✔
371
  }
1✔
372

373
  private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue,
374
      ResultMapping parentMapping, ResultSet rs) throws SQLException {
375
    if (parentMapping != null) {
1✔
376
      linkToParents(rs, parentMapping, rowValue);
1✔
377
    } else {
378
      callResultHandler(resultHandler, resultContext, rowValue);
1✔
379
    }
380
  }
1✔
381

382
  @SuppressWarnings("unchecked" /* because ResultHandler<?> is always ResultHandler<Object> */)
383
  private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext,
384
      Object rowValue) {
385
    resultContext.nextResultObject(rowValue);
1✔
386
    ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
1✔
387
  }
1✔
388

389
  private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
390
    return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
1✔
391
  }
392

393
  private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
394
    if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
1✔
395
      if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
1!
396
        rs.absolute(rowBounds.getOffset());
1✔
397
      }
398
    } else {
399
      for (int i = 0; i < rowBounds.getOffset(); i++) {
1✔
400
        if (!rs.next()) {
1!
401
          break;
×
402
        }
403
      }
404
    }
405
  }
1✔
406

407
  //
408
  // GET VALUE FROM ROW FOR SIMPLE RESULT MAP
409
  //
410

411
  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, CacheKey parentRowKey)
412
      throws SQLException {
413
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
1✔
414
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix, parentRowKey);
1✔
415
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
1✔
416
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
1✔
417
      boolean foundValues = this.useConstructorMappings;
1✔
418
      if (shouldApplyAutomaticMappings(resultMap, false)) {
1✔
419
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
1✔
420
      }
421
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
1✔
422
      foundValues = lazyLoader.size() > 0 || foundValues;
1✔
423
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
1✔
424
    }
425
    return rowValue;
1✔
426
  }
427

428
  //
429
  // GET VALUE FROM ROW FOR NESTED RESULT MAP
430
  //
431

432
  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix,
433
      Object partialObject) throws SQLException {
434
    final String resultMapId = resultMap.getId();
1✔
435
    Object rowValue = partialObject;
1✔
436
    if (rowValue != null) {
1✔
437
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
1✔
438
      putAncestor(rowValue, resultMapId);
1✔
439
      applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
1✔
440
      ancestorObjects.remove(resultMapId);
1✔
441
    } else {
1✔
442
      final ResultLoaderMap lazyLoader = new ResultLoaderMap();
1✔
443
      rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix, combinedKey);
1✔
444
      if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
1✔
445
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
1✔
446
        boolean foundValues = this.useConstructorMappings;
1✔
447
        if (shouldApplyAutomaticMappings(resultMap, true)) {
1✔
448
          foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
1!
449
        }
450
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
1✔
451
        putAncestor(rowValue, resultMapId);
1✔
452
        foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true)
1✔
453
            || foundValues;
454
        ancestorObjects.remove(resultMapId);
1✔
455
        foundValues = lazyLoader.size() > 0 || foundValues;
1✔
456
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
1✔
457
      }
458
      if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
459
        nestedResultObjects.put(combinedKey, rowValue);
1✔
460
      }
461
    }
462
    return rowValue;
1✔
463
  }
464

465
  private void putAncestor(Object resultObject, String resultMapId) {
466
    ancestorObjects.put(resultMapId, resultObject);
1✔
467
  }
1✔
468

469
  private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
470
    if (resultMap.getAutoMapping() != null) {
1✔
471
      return resultMap.getAutoMapping();
1✔
472
    }
473
    if (isNested) {
1✔
474
      return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
1✔
475
    } else {
476
      return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
1✔
477
    }
478
  }
479

480
  //
481
  // PROPERTY MAPPINGS
482
  //
483

484
  private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
485
      ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
486
    final Set<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
1✔
487
    boolean foundValues = false;
1✔
488
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
1✔
489
    for (ResultMapping propertyMapping : propertyMappings) {
1✔
490
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
1✔
491
      if (propertyMapping.getNestedResultMapId() != null) {
1✔
492
        // the user added a column attribute to a nested result map, ignore it
493
        column = null;
1✔
494
      }
495
      if (propertyMapping.isCompositeResult()
1✔
496
          || column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))
1✔
497
          || propertyMapping.getResultSet() != null) {
1✔
498
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader,
1✔
499
            columnPrefix);
500
        // issue #541 make property optional
501
        final String property = propertyMapping.getProperty();
1✔
502
        if (property == null) {
1✔
503
          continue;
1✔
504
        }
505
        if (value == DEFERRED) {
1✔
506
          foundValues = true;
1✔
507
          continue;
1✔
508
        }
509
        if (value != null) {
1✔
510
          foundValues = true;
1✔
511
        }
512
        if (value != null
1✔
513
            || configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()) {
1!
514
          // gcode issue #377, call setter on nulls (value is not 'found')
515
          metaObject.setValue(property, value);
1✔
516
        }
517
      }
518
    }
1✔
519
    return foundValues;
1✔
520
  }
521

522
  private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
523
      ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
524
    if (propertyMapping.getNestedQueryId() != null) {
1✔
525
      return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
1✔
526
    }
527
    if (propertyMapping.getResultSet() != null) {
1✔
528
      addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
1✔
529
      return DEFERRED;
1✔
530
    } else {
531
      final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
1✔
532
      final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
1✔
533
      return typeHandler.getResult(rs, column);
1✔
534
    }
535
  }
536

537
  private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap,
538
      MetaObject metaObject, String columnPrefix) throws SQLException {
539
    final String mapKey = resultMap.getId() + ":" + columnPrefix;
1✔
540
    List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
1✔
541
    if (autoMapping == null) {
1✔
542
      autoMapping = new ArrayList<>();
1✔
543
      final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
1✔
544
      // Remove the entry to release the memory
545
      List<String> mappedInConstructorAutoMapping = constructorAutoMappingColumns.remove(mapKey);
1✔
546
      if (mappedInConstructorAutoMapping != null) {
1✔
547
        unmappedColumnNames.removeAll(mappedInConstructorAutoMapping);
1✔
548
      }
549
      for (String columnName : unmappedColumnNames) {
1✔
550
        String propertyName = columnName;
1✔
551
        if (columnPrefix != null && !columnPrefix.isEmpty()) {
1!
552
          // When columnPrefix is specified,
553
          // ignore columns without the prefix.
554
          if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
555
            continue;
1✔
556
          }
557
          propertyName = columnName.substring(columnPrefix.length());
1✔
558
        }
559
        final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
1✔
560
        if (property != null && metaObject.hasSetter(property)) {
1✔
561
          if (resultMap.getMappedProperties().contains(property)) {
1✔
562
            continue;
1✔
563
          }
564
          final Class<?> propertyType = metaObject.getSetterType(property);
1✔
565
          if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
1✔
566
            final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
1✔
567
            autoMapping
1✔
568
                .add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
1✔
569
          } else {
1✔
570
            configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property,
1✔
571
                propertyType);
572
          }
573
        } else {
1✔
574
          configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName,
1✔
575
              property != null ? property : propertyName, null);
1✔
576
        }
577
      }
1✔
578
      autoMappingsCache.put(mapKey, autoMapping);
1✔
579
    }
580
    return autoMapping;
1✔
581
  }
582

583
  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
584
      String columnPrefix) throws SQLException {
585
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
1✔
586
    boolean foundValues = false;
1✔
587
    if (!autoMapping.isEmpty()) {
1✔
588
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
1✔
589
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
1✔
590
        if (value != null) {
1✔
591
          foundValues = true;
1✔
592
        }
593
        if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) {
1!
594
          // gcode issue #377, call setter on nulls (value is not 'found')
595
          metaObject.setValue(mapping.property, value);
1✔
596
        }
597
      }
1✔
598
    }
599
    return foundValues;
1✔
600
  }
601

602
  // MULTIPLE RESULT SETS
603

604
  private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException {
605
    CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(),
1✔
606
        parentMapping.getForeignColumn());
1✔
607
    List<PendingRelation> parents = pendingRelations.get(parentKey);
1✔
608
    if (parents != null) {
1✔
609
      for (PendingRelation parent : parents) {
1✔
610
        if (parent != null && rowValue != null) {
1!
611
          linkObjects(parent.metaObject, parent.propertyMapping, rowValue);
1✔
612
        }
613
      }
1✔
614
    }
615
  }
1✔
616

617
  private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, ResultMapping parentMapping)
618
      throws SQLException {
619
    CacheKey cacheKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(),
1✔
620
        parentMapping.getColumn());
1✔
621
    PendingRelation deferLoad = new PendingRelation();
1✔
622
    deferLoad.metaObject = metaResultObject;
1✔
623
    deferLoad.propertyMapping = parentMapping;
1✔
624
    List<PendingRelation> relations = MapUtil.computeIfAbsent(pendingRelations, cacheKey, k -> new ArrayList<>());
1✔
625
    // issue #255
626
    relations.add(deferLoad);
1✔
627
    ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet());
1✔
628
    if (previous == null) {
1✔
629
      nextResultMaps.put(parentMapping.getResultSet(), parentMapping);
1✔
630
    } else if (!previous.equals(parentMapping)) {
1!
631
      throw new ExecutorException("Two different properties are mapped to the same resultSet");
×
632
    }
633
  }
1✔
634

635
  private CacheKey createKeyForMultipleResults(ResultSet rs, ResultMapping resultMapping, String names, String columns)
636
      throws SQLException {
637
    CacheKey cacheKey = new CacheKey();
1✔
638
    cacheKey.update(resultMapping);
1✔
639
    if (columns != null && names != null) {
1!
640
      String[] columnsArray = columns.split(",");
1✔
641
      String[] namesArray = names.split(",");
1✔
642
      for (int i = 0; i < columnsArray.length; i++) {
1✔
643
        Object value = rs.getString(columnsArray[i]);
1✔
644
        if (value != null) {
1!
645
          cacheKey.update(namesArray[i]);
1✔
646
          cacheKey.update(value);
1✔
647
        }
648
      }
649
    }
650
    return cacheKey;
1✔
651
  }
652

653
  //
654
  // INSTANTIATION & CONSTRUCTOR MAPPING
655
  //
656

657
  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,
658
      String columnPrefix, CacheKey parentRowKey) throws SQLException {
659
    this.useConstructorMappings = false; // reset previous mapping result
1✔
660
    final List<Class<?>> constructorArgTypes = new ArrayList<>();
1✔
661
    final List<Object> constructorArgs = new ArrayList<>();
1✔
662

663
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix,
1✔
664
        parentRowKey);
665
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
1✔
666
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
1✔
667
      for (ResultMapping propertyMapping : propertyMappings) {
1✔
668
        // issue gcode #109 && issue #149
669
        if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
1✔
670
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration,
1✔
671
              objectFactory, constructorArgTypes, constructorArgs);
672
          break;
1✔
673
        }
674
      }
1✔
675

676
      // (issue #101)
677
      if (resultMap.hasResultMapsUsingConstructorCollection() && resultObject instanceof PendingConstructorCreation) {
1!
678
        linkNestedPendingCreations(rsw, resultMap, columnPrefix, parentRowKey,
1✔
679
            (PendingConstructorCreation) resultObject, constructorArgs);
680
      }
681
    }
682

683
    this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
1✔
684
    return resultObject;
1✔
685
  }
686

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

690
    final Class<?> resultType = resultMap.getType();
1✔
691
    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
1✔
692
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
1✔
693
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
1✔
694
      return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
1✔
695
    }
696
    if (!constructorMappings.isEmpty()) {
1✔
697
      return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs,
1✔
698
          columnPrefix, resultMap.hasResultMapsUsingConstructorCollection(), parentRowKey);
1✔
699
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
1✔
700
      return objectFactory.create(resultType);
1✔
701
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
1!
702
      return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes,
1✔
703
          constructorArgs);
704
    }
705
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
×
706
  }
707

708
  Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType,
709
      List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
710
      String columnPrefix, boolean useCollectionConstructorInjection, CacheKey parentRowKey) {
711
    boolean foundValues = false;
1✔
712

713
    for (ResultMapping constructorMapping : constructorMappings) {
1✔
714
      final Class<?> parameterType = constructorMapping.getJavaType();
1✔
715
      final String column = constructorMapping.getColumn();
1✔
716
      final Object value;
717
      try {
718
        if (constructorMapping.getNestedQueryId() != null) {
1✔
719
          value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
1✔
720
        } else if (constructorMapping.getNestedResultMapId() != null) {
1✔
721
          final String constructorColumnPrefix = getColumnPrefix(columnPrefix, constructorMapping);
1✔
722
          final ResultMap resultMap = resolveDiscriminatedResultMap(rsw.getResultSet(),
1✔
723
              configuration.getResultMap(constructorMapping.getNestedResultMapId()), constructorColumnPrefix);
1✔
724
          value = getRowValue(rsw, resultMap, constructorColumnPrefix,
1✔
725
              useCollectionConstructorInjection ? parentRowKey : null);
1✔
726
        } else {
1✔
727
          final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
1✔
728
          value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
1✔
729
        }
730
      } catch (ResultMapException | SQLException e) {
1✔
731
        throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
1✔
732
      }
1✔
733

734
      constructorArgTypes.add(parameterType);
1✔
735
      constructorArgs.add(value);
1✔
736

737
      foundValues = value != null || foundValues;
1✔
738
    }
1✔
739

740
    if (!foundValues) {
1✔
741
      return null;
1✔
742
    }
743

744
    if (useCollectionConstructorInjection) {
1✔
745
      // at least one of the nestedResultMaps contained a collection, we have to defer until later
746
      return new PendingConstructorCreation(resultType, constructorArgTypes, constructorArgs);
1✔
747
    }
748

749
    return objectFactory.create(resultType, constructorArgTypes, constructorArgs);
1✔
750
  }
751

752
  private Object createByConstructorSignature(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
753
      Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
754
    return applyConstructorAutomapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs,
1✔
755
        findConstructorForAutomapping(resultType, rsw).orElseThrow(() -> new ExecutorException(
1✔
756
            "No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames())));
×
757
  }
758

759
  private Optional<Constructor<?>> findConstructorForAutomapping(final Class<?> resultType, ResultSetWrapper rsw) {
760
    Constructor<?>[] constructors = resultType.getDeclaredConstructors();
1✔
761
    if (constructors.length == 1) {
1✔
762
      return Optional.of(constructors[0]);
1✔
763
    }
764
    Optional<Constructor<?>> annotated = Arrays.stream(constructors)
1✔
765
        .filter(x -> x.isAnnotationPresent(AutomapConstructor.class)).reduce((x, y) -> {
1✔
766
          throw new ExecutorException("@AutomapConstructor should be used in only one constructor.");
1✔
767
        });
768
    if (annotated.isPresent()) {
1✔
769
      return annotated;
1✔
770
    }
771
    if (configuration.isArgNameBasedConstructorAutoMapping()) {
1!
772
      // Finding-best-match type implementation is possible,
773
      // but using @AutomapConstructor seems sufficient.
774
      throw new ExecutorException(MessageFormat.format(
×
775
          "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.",
776
          resultType.getName()));
×
777
    } else {
778
      return Arrays.stream(constructors).filter(x -> findUsableConstructorByArgTypes(x, rsw.getJdbcTypes())).findAny();
1✔
779
    }
780
  }
781

782
  private boolean findUsableConstructorByArgTypes(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) {
783
    final Class<?>[] parameterTypes = constructor.getParameterTypes();
1✔
784
    if (parameterTypes.length != jdbcTypes.size()) {
1✔
785
      return false;
1✔
786
    }
787
    for (int i = 0; i < parameterTypes.length; i++) {
1✔
788
      if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) {
1!
789
        return false;
×
790
      }
791
    }
792
    return true;
1✔
793
  }
794

795
  private Object applyConstructorAutomapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
796
      Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor)
797
      throws SQLException {
798
    boolean foundValues = false;
1✔
799
    if (configuration.isArgNameBasedConstructorAutoMapping()) {
1✔
800
      foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, constructorArgTypes,
1✔
801
          constructorArgs, constructor, foundValues);
802
    } else {
803
      foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor,
1✔
804
          foundValues);
805
    }
806
    return foundValues || configuration.isReturnInstanceForEmptyRow()
1!
807
        ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
1✔
808
  }
809

810
  private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,
811
      List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {
812
    Class<?>[] parameterTypes = constructor.getParameterTypes();
1✔
813
    for (int i = 0; i < parameterTypes.length; i++) {
1✔
814
      Class<?> parameterType = parameterTypes[i];
1✔
815
      String columnName = rsw.getColumnNames().get(i);
1✔
816
      TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
1✔
817
      Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
818
      constructorArgTypes.add(parameterType);
1✔
819
      constructorArgs.add(value);
1✔
820
      foundValues = value != null || foundValues;
1!
821
    }
822
    return foundValues;
1✔
823
  }
824

825
  private boolean applyArgNameBasedConstructorAutoMapping(ResultSetWrapper rsw, ResultMap resultMap,
826
      String columnPrefix, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor,
827
      boolean foundValues) throws SQLException {
828
    List<String> missingArgs = null;
1✔
829
    Parameter[] params = constructor.getParameters();
1✔
830
    for (Parameter param : params) {
1✔
831
      boolean columnNotFound = true;
1✔
832
      Param paramAnno = param.getAnnotation(Param.class);
1✔
833
      String paramName = paramAnno == null ? param.getName() : paramAnno.value();
1✔
834
      for (String columnName : rsw.getColumnNames()) {
1✔
835
        if (columnMatchesParam(columnName, paramName, columnPrefix)) {
1✔
836
          Class<?> paramType = param.getType();
1✔
837
          TypeHandler<?> typeHandler = rsw.getTypeHandler(paramType, columnName);
1✔
838
          Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
839
          constructorArgTypes.add(paramType);
1✔
840
          constructorArgs.add(value);
1✔
841
          final String mapKey = resultMap.getId() + ":" + columnPrefix;
1✔
842
          if (!autoMappingsCache.containsKey(mapKey)) {
1!
843
            MapUtil.computeIfAbsent(constructorAutoMappingColumns, mapKey, k -> new ArrayList<>()).add(columnName);
1✔
844
          }
845
          columnNotFound = false;
1✔
846
          foundValues = value != null || foundValues;
1!
847
        }
848
      }
1✔
849
      if (columnNotFound) {
1✔
850
        if (missingArgs == null) {
1!
851
          missingArgs = new ArrayList<>();
1✔
852
        }
853
        missingArgs.add(paramName);
1✔
854
      }
855
    }
856
    if (foundValues && constructorArgs.size() < params.length) {
1✔
857
      throw new ExecutorException(MessageFormat.format(
1✔
858
          "Constructor auto-mapping of ''{1}'' failed " + "because ''{0}'' were not found in the result set; "
859
              + "Available columns are ''{2}'' and mapUnderscoreToCamelCase is ''{3}''.",
860
          missingArgs, constructor, rsw.getColumnNames(), configuration.isMapUnderscoreToCamelCase()));
1✔
861
    }
862
    return foundValues;
1✔
863
  }
864

865
  private boolean columnMatchesParam(String columnName, String paramName, String columnPrefix) {
866
    if (columnPrefix != null) {
1✔
867
      if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
868
        return false;
1✔
869
      }
870
      columnName = columnName.substring(columnPrefix.length());
1✔
871
    }
872
    return paramName
1✔
873
        .equalsIgnoreCase(configuration.isMapUnderscoreToCamelCase() ? columnName.replace("_", "") : columnName);
1✔
874
  }
875

876
  private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
877
      throws SQLException {
878
    final Class<?> resultType = resultMap.getType();
1✔
879
    final String columnName;
880
    if (!resultMap.getResultMappings().isEmpty()) {
1✔
881
      final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
1✔
882
      final ResultMapping mapping = resultMappingList.get(0);
1✔
883
      columnName = prependPrefix(mapping.getColumn(), columnPrefix);
1✔
884
    } else {
1✔
885
      columnName = rsw.getColumnNames().get(0);
1✔
886
    }
887
    final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
1✔
888
    return typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
889
  }
890

891
  //
892
  // NESTED QUERY
893
  //
894

895
  private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)
896
      throws SQLException {
897
    final String nestedQueryId = constructorMapping.getNestedQueryId();
1✔
898
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
1✔
899
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
1✔
900
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping,
1✔
901
        nestedQueryParameterType, columnPrefix);
902
    Object value = null;
1✔
903
    if (nestedQueryParameterObject != null) {
1!
904
      final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
1✔
905
      final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
1✔
906
          nestedBoundSql);
907
      final Class<?> targetType = constructorMapping.getJavaType();
1✔
908
      final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
1✔
909
          nestedQueryParameterObject, targetType, key, nestedBoundSql);
910
      value = resultLoader.loadResult();
1✔
911
    }
912
    return value;
1✔
913
  }
914

915
  private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
916
      ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
917
    final String nestedQueryId = propertyMapping.getNestedQueryId();
1✔
918
    final String property = propertyMapping.getProperty();
1✔
919
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
1✔
920
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
1✔
921
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping,
1✔
922
        nestedQueryParameterType, columnPrefix);
923
    Object value = null;
1✔
924
    if (nestedQueryParameterObject != null) {
1✔
925
      final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
1✔
926
      final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
1✔
927
          nestedBoundSql);
928
      final Class<?> targetType = propertyMapping.getJavaType();
1✔
929
      if (executor.isCached(nestedQuery, key)) {
1✔
930
        executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
1✔
931
        value = DEFERRED;
1✔
932
      } else {
933
        final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
1✔
934
            nestedQueryParameterObject, targetType, key, nestedBoundSql);
935
        if (propertyMapping.isLazy()) {
1✔
936
          lazyLoader.addLoader(property, metaResultObject, resultLoader);
1✔
937
          value = DEFERRED;
1✔
938
        } else {
939
          value = resultLoader.loadResult();
1✔
940
        }
941
      }
942
    }
943
    return value;
1✔
944
  }
945

946
  private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
947
      String columnPrefix) throws SQLException {
948
    if (resultMapping.isCompositeResult()) {
1✔
949
      return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix);
1✔
950
    }
951
    return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix);
1✔
952
  }
953

954
  private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
955
      String columnPrefix) throws SQLException {
956
    final TypeHandler<?> typeHandler;
957
    if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
1✔
958
      typeHandler = typeHandlerRegistry.getTypeHandler(parameterType);
1✔
959
    } else {
960
      typeHandler = typeHandlerRegistry.getUnknownTypeHandler();
1✔
961
    }
962
    return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
1✔
963
  }
964

965
  private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
966
      String columnPrefix) throws SQLException {
967
    final Object parameterObject = instantiateParameterObject(parameterType);
1✔
968
    final MetaObject metaObject = configuration.newMetaObject(parameterObject);
1✔
969
    boolean foundValues = false;
1✔
970
    for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
1✔
971
      final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty());
1✔
972
      final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType);
1✔
973
      final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix));
1✔
974
      // issue #353 & #560 do not execute nested query if key is null
975
      if (propValue != null) {
1✔
976
        metaObject.setValue(innerResultMapping.getProperty(), propValue);
1✔
977
        foundValues = true;
1✔
978
      }
979
    }
1✔
980
    return foundValues ? parameterObject : null;
1✔
981
  }
982

983
  private Object instantiateParameterObject(Class<?> parameterType) {
984
    if (parameterType == null) {
1✔
985
      return new HashMap<>();
1✔
986
    }
987
    if (ParamMap.class.equals(parameterType)) {
1✔
988
      return new HashMap<>(); // issue #649
1✔
989
    } else {
990
      return objectFactory.create(parameterType);
1✔
991
    }
992
  }
993

994
  //
995
  // DISCRIMINATOR
996
  //
997

998
  public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)
999
      throws SQLException {
1000
    Set<String> pastDiscriminators = new HashSet<>();
1✔
1001
    Discriminator discriminator = resultMap.getDiscriminator();
1✔
1002
    while (discriminator != null) {
1✔
1003
      final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
1✔
1004
      final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
1✔
1005
      if (!configuration.hasResultMap(discriminatedMapId)) {
1✔
1006
        break;
1✔
1007
      }
1008
      resultMap = configuration.getResultMap(discriminatedMapId);
1✔
1009
      Discriminator lastDiscriminator = discriminator;
1✔
1010
      discriminator = resultMap.getDiscriminator();
1✔
1011
      if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
1!
1012
        break;
1✔
1013
      }
1014
    }
1✔
1015
    return resultMap;
1✔
1016
  }
1017

1018
  private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix)
1019
      throws SQLException {
1020
    final ResultMapping resultMapping = discriminator.getResultMapping();
1✔
1021
    final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
1✔
1022
    return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
1✔
1023
  }
1024

1025
  private String prependPrefix(String columnName, String prefix) {
1026
    if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
1!
1027
      return columnName;
1✔
1028
    }
1029
    return prefix + columnName;
1✔
1030
  }
1031

1032
  //
1033
  // HANDLE NESTED RESULT MAPS
1034
  //
1035

1036
  private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap,
1037
      ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
1038
    final boolean useCollectionConstructorInjection = resultMap.hasResultMapsUsingConstructorCollection();
1✔
1039
    boolean verifyPendingCreationResult = true;
1✔
1040
    PendingConstructorCreation lastHandledCreation = null;
1✔
1041
    if (useCollectionConstructorInjection) {
1✔
1042
      verifyPendingCreationPreconditions(parentMapping);
1✔
1043
    }
1044

1045
    final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
1✔
1046
    ResultSet resultSet = rsw.getResultSet();
1✔
1047
    skipRows(resultSet, rowBounds);
1✔
1048
    Object rowValue = previousRowValue;
1✔
1049

1050
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
1!
1051
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
1✔
1052
      final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
1✔
1053

1054
      final Object partialObject = nestedResultObjects.get(rowKey);
1✔
1055
      final boolean foundNewUniqueRow = partialObject == null;
1✔
1056

1057
      // issue #577, #542 && #101
1058
      if (useCollectionConstructorInjection) {
1✔
1059
        if (foundNewUniqueRow && lastHandledCreation != null) {
1✔
1060
          createAndStorePendingCreation(resultHandler, resultSet, resultContext, lastHandledCreation,
1✔
1061
              verifyPendingCreationResult);
1062
          lastHandledCreation = null;
1✔
1063
          // we only need to verify the first the result for a given result set
1064
          // as we can assume the next result will look exactly the same w.r.t its mapping
1065
          verifyPendingCreationResult = false;
1✔
1066
        }
1067

1068
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1069
        if (rowValue instanceof PendingConstructorCreation) {
1!
1070
          lastHandledCreation = (PendingConstructorCreation) rowValue;
1✔
1071
        }
1072
      } else if (mappedStatement.isResultOrdered()) {
1✔
1073
        if (foundNewUniqueRow && rowValue != null) {
1✔
1074
          nestedResultObjects.clear();
1✔
1075
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1076
        }
1077
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1078
      } else {
1079
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1080
        if (foundNewUniqueRow) {
1✔
1081
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1082
        }
1083
      }
1084
    }
1✔
1085

1086
    if (useCollectionConstructorInjection && lastHandledCreation != null) {
1!
1087
      createAndStorePendingCreation(resultHandler, resultSet, resultContext, lastHandledCreation,
1✔
1088
          verifyPendingCreationResult);
1089
    } else if (rowValue != null && mappedStatement.isResultOrdered()
1✔
1090
        && shouldProcessMoreRows(resultContext, rowBounds)) {
1✔
1091
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1092
      previousRowValue = null;
1✔
1093
    } else if (rowValue != null) {
1✔
1094
      previousRowValue = rowValue;
1✔
1095
    }
1096
  }
1✔
1097

1098
  //
1099
  // NESTED RESULT MAP (PENDING CONSTRUCTOR CREATIONS)
1100
  //
1101
  private void linkNestedPendingCreations(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
1102
      CacheKey parentRowKey, PendingConstructorCreation pendingCreation, List<Object> constructorArgs)
1103
      throws SQLException {
1104
    final CacheKey rowKey = createRowKey(resultMap, rsw, columnPrefix);
1✔
1105
    final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
1106

1107
    if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1!
1108
      nestedResultObjects.put(combinedKey, pendingCreation);
1✔
1109
    }
1110

1111
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
1✔
1112
    for (int index = 0; index < constructorMappings.size(); index++) {
1✔
1113
      final ResultMapping constructorMapping = constructorMappings.get(index);
1✔
1114
      final String nestedResultMapId = constructorMapping.getNestedResultMapId();
1✔
1115

1116
      if (nestedResultMapId == null) {
1✔
1117
        continue;
1✔
1118
      }
1119

1120
      final Class<?> javaType = constructorMapping.getJavaType();
1✔
1121
      if (javaType == null || !objectFactory.isCollection(javaType)) {
1!
1122
        continue;
1✔
1123
      }
1124

1125
      final String constructorColumnPrefix = getColumnPrefix(columnPrefix, constructorMapping);
1✔
1126
      final ResultMap nestedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(),
1✔
1127
          configuration.getResultMap(constructorMapping.getNestedResultMapId()), constructorColumnPrefix);
1✔
1128

1129
      final Object actualValue = constructorArgs.get(index);
1✔
1130
      final boolean hasValue = actualValue != null;
1✔
1131
      final boolean isInnerCreation = actualValue instanceof PendingConstructorCreation;
1✔
1132
      final boolean alreadyCreatedCollection = hasValue && objectFactory.isCollection(actualValue.getClass());
1!
1133

1134
      if (!isInnerCreation) {
1✔
1135
        final Collection<Object> value = pendingCreation.initializeCollectionForResultMapping(objectFactory,
1✔
1136
            nestedResultMap, constructorMapping, index);
1✔
1137
        if (!alreadyCreatedCollection) {
1!
1138
          // override values with empty collection
1139
          constructorArgs.set(index, value);
1✔
1140
        }
1141

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

1146
        if (nestedCombinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
1147
          nestedResultObjects.put(nestedCombinedKey, pendingCreation);
1✔
1148
        }
1149

1150
        if (hasValue) {
1✔
1151
          pendingCreation.linkCollectionValue(constructorMapping, actualValue);
1✔
1152
        }
1153
      } else {
1✔
1154
        final PendingConstructorCreation innerCreation = (PendingConstructorCreation) actualValue;
1✔
1155
        final Collection<Object> value = pendingCreation.initializeCollectionForResultMapping(objectFactory,
1✔
1156
            nestedResultMap, constructorMapping, index);
1✔
1157
        // we will fill this collection when building the final object
1158
        constructorArgs.set(index, value);
1✔
1159
        // link the creation for building later
1160
        pendingCreation.linkCreation(nestedResultMap, innerCreation);
1✔
1161
      }
1162
    }
1163
  }
1✔
1164

1165
  private boolean applyNestedPendingConstructorCreations(ResultSetWrapper rsw, ResultMap resultMap,
1166
      MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject, boolean foundValues) {
1167
    for (ResultMapping constructorMapping : resultMap.getConstructorResultMappings()) {
1✔
1168
      final String nestedResultMapId = constructorMapping.getNestedResultMapId();
1✔
1169
      final Class<?> parameterType = constructorMapping.getJavaType();
1✔
1170
      if (nestedResultMapId == null || constructorMapping.getResultSet() != null || parameterType == null
1!
1171
          || !objectFactory.isCollection(parameterType)) {
1✔
1172
        continue;
1✔
1173
      }
1174

1175
      try {
1176
        final String columnPrefix = getColumnPrefix(parentPrefix, constructorMapping);
1✔
1177
        final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
1✔
1178

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

1182
        if (newObject) {
1✔
1183
          // new objects are linked by createResultObject
1184
          continue;
1✔
1185
        }
1186

1187
        // should have inserted already as a nested result object
1188
        Object rowValue = nestedResultObjects.get(combinedKey);
1✔
1189

1190
        PendingConstructorCreation pendingConstructorCreation = null;
1✔
1191
        if (rowValue instanceof PendingConstructorCreation) {
1✔
1192
          pendingConstructorCreation = (PendingConstructorCreation) rowValue;
1✔
1193
        }
1194

1195
        final boolean newValueForNestedResultMap = pendingConstructorCreation == null;
1✔
1196
        if (newValueForNestedResultMap) {
1✔
1197
          final Object parentObject = metaObject.getOriginalObject();
1✔
1198
          if (!(parentObject instanceof PendingConstructorCreation)) {
1!
NEW
1199
            throw new ExecutorException(
×
1200
                "parentObject is not a pending creation, cannot continue linking! MyBatis internal error!");
1201
          }
1202

1203
          pendingConstructorCreation = (PendingConstructorCreation) parentObject;
1✔
1204
        }
1205

1206
        rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix,
1✔
1207
            newValueForNestedResultMap ? null : pendingConstructorCreation);
1✔
1208

1209
        if (rowValue == null) {
1!
NEW
1210
          continue;
×
1211
        }
1212

1213
        if (rowValue instanceof PendingConstructorCreation) {
1✔
1214
          if (newValueForNestedResultMap) {
1✔
1215
            // we created a brand new pcc. this is a new collection value
1216
            pendingConstructorCreation.linkCreation(nestedResultMap, (PendingConstructorCreation) rowValue);
1✔
1217
            foundValues = true;
1✔
1218
          }
1219
        } else {
1220
          pendingConstructorCreation.linkCollectionValue(constructorMapping, rowValue);
1✔
1221
          foundValues = true;
1✔
1222

1223
          if (combinedKey != CacheKey.NULL_CACHE_KEY) {
1✔
1224
            nestedResultObjects.put(combinedKey, pendingConstructorCreation);
1✔
1225
          }
1226
        }
NEW
1227
      } catch (SQLException e) {
×
NEW
1228
        throw new ExecutorException("Error getting experimental nested result map values for '"
×
NEW
1229
            + constructorMapping.getProperty() + "'.  Cause: " + e, e);
×
1230
      }
1✔
1231
    }
1✔
1232
    return foundValues;
1✔
1233
  }
1234

1235
  private void verifyPendingCreationPreconditions(ResultMapping parentMapping) {
1236
    if (parentMapping != null) {
1!
NEW
1237
      throw new ExecutorException(
×
1238
          "Cannot construct objects with collections in constructors using multiple result sets yet!");
1239
    }
1240

1241
    if (!mappedStatement.isResultOrdered()) {
1!
NEW
1242
      throw new ExecutorException("Cannot reliably construct result if we are not sure the results are ordered "
×
1243
          + "so that no new previous rows would occur, set resultOrdered on your mapped statement if you have verified this");
1244
    }
1245
  }
1✔
1246

1247
  private void createAndStorePendingCreation(ResultHandler<?> resultHandler, ResultSet resultSet,
1248
      DefaultResultContext<Object> resultContext, PendingConstructorCreation pendingCreation, boolean shouldVerify)
1249
      throws SQLException {
1250
    final Object result = pendingCreation.create(objectFactory, shouldVerify);
1✔
1251
    storeObject(resultHandler, resultContext, result, null, resultSet);
1✔
1252
    nestedResultObjects.clear();
1✔
1253
  }
1✔
1254

1255
  //
1256
  // NESTED RESULT MAP (JOIN MAPPING)
1257
  //
1258

1259
  private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
1260
      String parentPrefix, CacheKey parentRowKey, boolean newObject) {
1261
    boolean foundValues = false;
1✔
1262
    for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
1✔
1263
      final String nestedResultMapId = resultMapping.getNestedResultMapId();
1✔
1264
      if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
1!
1265
        try {
1266
          final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
1✔
1267
          final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
1✔
1268
          if (resultMapping.getColumnPrefix() == null) {
1✔
1269
            // try to fill circular reference only when columnPrefix
1270
            // is not specified for the nested result map (issue #215)
1271
            Object ancestorObject = ancestorObjects.get(nestedResultMapId);
1✔
1272
            if (ancestorObject != null) {
1✔
1273
              if (newObject) {
1✔
1274
                linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
1✔
1275
              }
1276
              continue;
1✔
1277
            }
1278
          }
1279
          final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
1✔
1280
          final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
1281
          Object rowValue = nestedResultObjects.get(combinedKey);
1✔
1282
          boolean knownValue = rowValue != null;
1✔
1283
          instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
1✔
1284
          if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
1✔
1285
            rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
1✔
1286
            if (rowValue != null && !knownValue) {
1✔
1287
              linkObjects(metaObject, resultMapping, rowValue);
1✔
1288
              foundValues = true;
1✔
1289
            }
1290
          }
1291
        } catch (SQLException e) {
×
1292
          throw new ExecutorException(
×
1293
              "Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
×
1294
        }
1✔
1295
      }
1296
    }
1✔
1297

1298
    // (issue #101)
1299
    if (resultMap.hasResultMapsUsingConstructorCollection()) {
1✔
1300
      foundValues = applyNestedPendingConstructorCreations(rsw, resultMap, metaObject, parentPrefix, parentRowKey,
1✔
1301
          newObject, foundValues);
1302
    }
1303

1304
    return foundValues;
1✔
1305
  }
1306

1307
  private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) {
1308
    final StringBuilder columnPrefixBuilder = new StringBuilder();
1✔
1309
    if (parentPrefix != null) {
1✔
1310
      columnPrefixBuilder.append(parentPrefix);
1✔
1311
    }
1312
    if (resultMapping.getColumnPrefix() != null) {
1✔
1313
      columnPrefixBuilder.append(resultMapping.getColumnPrefix());
1✔
1314
    }
1315
    return columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH);
1✔
1316
  }
1317

1318
  private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSetWrapper rsw)
1319
      throws SQLException {
1320
    Set<String> notNullColumns = resultMapping.getNotNullColumns();
1✔
1321
    if (notNullColumns != null && !notNullColumns.isEmpty()) {
1✔
1322
      ResultSet rs = rsw.getResultSet();
1✔
1323
      for (String column : notNullColumns) {
1✔
1324
        rs.getObject(prependPrefix(column, columnPrefix));
1✔
1325
        if (!rs.wasNull()) {
1✔
1326
          return true;
1✔
1327
        }
1328
      }
1✔
1329
      return false;
1✔
1330
    }
1331
    if (columnPrefix != null) {
1✔
1332
      for (String columnName : rsw.getColumnNames()) {
1✔
1333
        if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix.toUpperCase(Locale.ENGLISH))) {
1✔
1334
          return true;
1✔
1335
        }
1336
      }
1✔
1337
      return false;
1✔
1338
    }
1339
    return true;
1✔
1340
  }
1341

1342
  private ResultMap getNestedResultMap(ResultSet rs, String nestedResultMapId, String columnPrefix)
1343
      throws SQLException {
1344
    ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId);
1✔
1345
    return resolveDiscriminatedResultMap(rs, nestedResultMap, columnPrefix);
1✔
1346
  }
1347

1348
  //
1349
  // UNIQUE RESULT KEY
1350
  //
1351

1352
  private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
1353
    final CacheKey cacheKey = new CacheKey();
1✔
1354
    cacheKey.update(resultMap.getId());
1✔
1355
    List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
1✔
1356
    if (resultMappings.isEmpty()) {
1✔
1357
      if (Map.class.isAssignableFrom(resultMap.getType())) {
1!
1358
        createRowKeyForMap(rsw, cacheKey);
×
1359
      } else {
1360
        createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
1✔
1361
      }
1362
    } else {
1363
      createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
1✔
1364
    }
1365
    if (cacheKey.getUpdateCount() < 2) {
1✔
1366
      return CacheKey.NULL_CACHE_KEY;
1✔
1367
    }
1368
    return cacheKey;
1✔
1369
  }
1370

1371
  private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) {
1372
    if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) {
1✔
1373
      CacheKey combinedKey;
1374
      try {
1375
        combinedKey = rowKey.clone();
1✔
1376
      } catch (CloneNotSupportedException e) {
×
1377
        throw new ExecutorException("Error cloning cache key.  Cause: " + e, e);
×
1378
      }
1✔
1379
      combinedKey.update(parentRowKey);
1✔
1380
      return combinedKey;
1✔
1381
    }
1382
    return CacheKey.NULL_CACHE_KEY;
1✔
1383
  }
1384

1385
  private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {
1386
    List<ResultMapping> resultMappings = resultMap.getIdResultMappings();
1✔
1387
    if (resultMappings.isEmpty()) {
1✔
1388
      resultMappings = resultMap.getPropertyResultMappings();
1✔
1389
    }
1390
    return resultMappings;
1✔
1391
  }
1392

1393
  private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1394
      List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {
1395
    for (ResultMapping resultMapping : resultMappings) {
1✔
1396
      if (resultMapping.isSimple()) {
1✔
1397
        final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
1✔
1398
        final TypeHandler<?> th = resultMapping.getTypeHandler();
1✔
1399
        Set<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
1✔
1400
        // Issue #114
1401
        if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
1!
1402
          final Object value = th.getResult(rsw.getResultSet(), column);
1✔
1403
          if (value != null || configuration.isReturnInstanceForEmptyRow()) {
1✔
1404
            cacheKey.update(column);
1✔
1405
            cacheKey.update(value);
1✔
1406
          }
1407
        }
1408
      }
1409
    }
1✔
1410
  }
1✔
1411

1412
  private void createRowKeyForUnmappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1413
      String columnPrefix) throws SQLException {
1414
    final MetaClass metaType = MetaClass.forClass(resultMap.getType(), reflectorFactory);
1✔
1415
    List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
1✔
1416
    for (String column : unmappedColumnNames) {
1✔
1417
      String property = column;
1✔
1418
      if (columnPrefix != null && !columnPrefix.isEmpty()) {
1!
1419
        // When columnPrefix is specified, ignore columns without the prefix.
1420
        if (!column.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
1421
          continue;
1✔
1422
        }
1423
        property = column.substring(columnPrefix.length());
1✔
1424
      }
1425
      if (metaType.findProperty(property, configuration.isMapUnderscoreToCamelCase()) != null) {
1✔
1426
        String value = rsw.getResultSet().getString(column);
1✔
1427
        if (value != null) {
1✔
1428
          cacheKey.update(column);
1✔
1429
          cacheKey.update(value);
1✔
1430
        }
1431
      }
1432
    }
1✔
1433
  }
1✔
1434

1435
  private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException {
1436
    List<String> columnNames = rsw.getColumnNames();
×
1437
    for (String columnName : columnNames) {
×
1438
      final String value = rsw.getResultSet().getString(columnName);
×
1439
      if (value != null) {
×
1440
        cacheKey.update(columnName);
×
1441
        cacheKey.update(value);
×
1442
      }
1443
    }
×
1444
  }
×
1445

1446
  private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
1447
    final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1✔
1448
    if (collectionProperty != null) {
1✔
1449
      final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1✔
1450
      targetMetaObject.add(rowValue);
1✔
1451
    } else {
1✔
1452
      metaObject.setValue(resultMapping.getProperty(), rowValue);
1✔
1453
    }
1454
  }
1✔
1455

1456
  private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
1457
    final String propertyName = resultMapping.getProperty();
1✔
1458
    Object propertyValue = metaObject.getValue(propertyName);
1✔
1459
    if (propertyValue == null) {
1✔
1460
      Class<?> type = resultMapping.getJavaType();
1✔
1461
      if (type == null) {
1✔
1462
        type = metaObject.getSetterType(propertyName);
1✔
1463
      }
1464
      try {
1465
        if (objectFactory.isCollection(type)) {
1✔
1466
          propertyValue = objectFactory.create(type);
1✔
1467
          metaObject.setValue(propertyName, propertyValue);
1✔
1468
          return propertyValue;
1✔
1469
        }
1470
      } catch (Exception e) {
×
1471
        throw new ExecutorException(
×
1472
            "Error instantiating collection property for result '" + resultMapping.getProperty() + "'.  Cause: " + e,
×
1473
            e);
1474
      }
1✔
1475
    } else if (objectFactory.isCollection(propertyValue.getClass())) {
1✔
1476
      return propertyValue;
1✔
1477
    }
1478
    return null;
1✔
1479
  }
1480

1481
  private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
1482
    if (rsw.getColumnNames().size() == 1) {
1✔
1483
      return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0)));
1✔
1484
    }
1485
    return typeHandlerRegistry.hasTypeHandler(resultType);
1✔
1486
  }
1487

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

© 2025 Coveralls, Inc