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

mybatis / mybatis-3 / #2968

pending completion
#2968

push

github

web-flow
Merge pull request #2792 from hazendaz/formatting

[ci] Formatting

84 of 84 new or added lines in 23 files covered. (100.0%)

9412 of 10781 relevant lines covered (87.3%)

0.87 hits per line

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

93.36
/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java
1
/*
2
 *    Copyright 2009-2023 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.HashMap;
28
import java.util.HashSet;
29
import java.util.List;
30
import java.util.Locale;
31
import java.util.Map;
32
import java.util.Optional;
33
import java.util.Set;
34

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

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

79
  private static final Object DEFERRED = new Object();
1✔
80

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

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

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

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

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

108
  private static class PendingRelation {
109
    public MetaObject metaObject;
110
    public ResultMapping propertyMapping;
111
  }
112

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

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

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

141
  //
142
  // HANDLE OUTPUT PARAMETER
143
  //
144

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

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

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

192
    final List<Object> multipleResults = new ArrayList<>();
1✔
193

194
    int resultSetCount = 0;
1✔
195
    ResultSetWrapper rsw = getFirstResultSet(stmt);
1✔
196

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

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

223
    return collapseSingleResultList(multipleResults);
1✔
224
  }
225

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

230
    ResultSetWrapper rsw = getFirstResultSet(stmt);
1✔
231

232
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
1✔
233

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

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

244
  private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
245
    ResultSet rs = stmt.getResultSet();
1✔
246
    while (rs == null) {
1✔
247
      // move forward to get the first resultset in case the driver
248
      // doesn't return the resultset as the first result (HSQLDB 2.1)
249
      if (stmt.getMoreResults()) {
1✔
250
        rs = stmt.getResultSet();
×
251
      } else {
252
        if (stmt.getUpdateCount() == -1) {
1✔
253
          // no more results. Must be no resultset
254
          break;
1✔
255
        }
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
        if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
1✔
267
          ResultSet rs = stmt.getResultSet();
1✔
268
          if (rs == null) {
1✔
269
            return getNextResultSet(stmt);
×
270
          } else {
271
            return new ResultSetWrapper(rs, configuration);
1✔
272
          }
273
        }
274
      }
275
    } catch (Exception e) {
×
276
      // Intentionally ignored.
277
    }
1✔
278
    return null;
1✔
279
  }
280

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

291
  private void cleanUpAfterHandlingResultSet() {
292
    nestedResultObjects.clear();
1✔
293
  }
1✔
294

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

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

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

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

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

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

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

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

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

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

385
  private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
386
    return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
1✔
387
  }
388

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

403
  //
404
  // GET VALUE FROM ROW FOR SIMPLE RESULT MAP
405
  //
406

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

423
  //
424
  // GET VALUE FROM ROW FOR NESTED RESULT MAP
425
  //
426

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

460
  private void putAncestor(Object resultObject, String resultMapId) {
461
    ancestorObjects.put(resultMapId, resultObject);
1✔
462
  }
1✔
463

464
  private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
465
    if (resultMap.getAutoMapping() != null) {
1✔
466
      return resultMap.getAutoMapping();
1✔
467
    } else {
468
      if (isNested) {
1✔
469
        return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
1✔
470
      } else {
471
        return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
1✔
472
      }
473
    }
474
  }
475

476
  //
477
  // PROPERTY MAPPINGS
478
  //
479

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

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

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

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

597
  // MULTIPLE RESULT SETS
598

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

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

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

650
  //
651
  // INSTANTIATION & CONSTRUCTOR MAPPING
652
  //
653

654
  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,
655
      String columnPrefix) throws SQLException {
656
    this.useConstructorMappings = false; // reset previous mapping result
1✔
657
    final List<Class<?>> constructorArgTypes = new ArrayList<>();
1✔
658
    final List<Object> constructorArgs = new ArrayList<>();
1✔
659
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
1✔
660
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
1✔
661
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
1✔
662
      for (ResultMapping propertyMapping : propertyMappings) {
1✔
663
        // issue gcode #109 && issue #149
664
        if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
1✔
665
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration,
1✔
666
              objectFactory, constructorArgTypes, constructorArgs);
667
          break;
1✔
668
        }
669
      }
1✔
670
    }
671
    this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
1✔
672
    return resultObject;
1✔
673
  }
674

675
  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
676
      List<Object> constructorArgs, String columnPrefix) throws SQLException {
677
    final Class<?> resultType = resultMap.getType();
1✔
678
    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
1✔
679
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
1✔
680
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
1✔
681
      return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
1✔
682
    } else if (!constructorMappings.isEmpty()) {
1✔
683
      return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs,
1✔
684
          columnPrefix);
685
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
1✔
686
      return objectFactory.create(resultType);
1✔
687
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
1✔
688
      return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes,
1✔
689
          constructorArgs);
690
    }
691
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
×
692
  }
693

694
  Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType,
695
      List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
696
      String columnPrefix) {
697
    boolean foundValues = false;
1✔
698
    for (ResultMapping constructorMapping : constructorMappings) {
1✔
699
      final Class<?> parameterType = constructorMapping.getJavaType();
1✔
700
      final String column = constructorMapping.getColumn();
1✔
701
      final Object value;
702
      try {
703
        if (constructorMapping.getNestedQueryId() != null) {
1✔
704
          value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
1✔
705
        } else if (constructorMapping.getNestedResultMapId() != null) {
1✔
706
          final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
1✔
707
          value = getRowValue(rsw, resultMap, getColumnPrefix(columnPrefix, constructorMapping));
1✔
708
        } else {
1✔
709
          final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
1✔
710
          value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
1✔
711
        }
712
      } catch (ResultMapException | SQLException e) {
1✔
713
        throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
1✔
714
      }
1✔
715
      constructorArgTypes.add(parameterType);
1✔
716
      constructorArgs.add(value);
1✔
717
      foundValues = value != null || foundValues;
1✔
718
    }
1✔
719
    return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
1✔
720
  }
721

722
  private Object createByConstructorSignature(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
723
      Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
724
    return applyConstructorAutomapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs,
1✔
725
        findConstructorForAutomapping(resultType, rsw).orElseThrow(() -> new ExecutorException(
1✔
726
            "No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames())));
×
727
  }
728

729
  private Optional<Constructor<?>> findConstructorForAutomapping(final Class<?> resultType, ResultSetWrapper rsw) {
730
    Constructor<?>[] constructors = resultType.getDeclaredConstructors();
1✔
731
    if (constructors.length == 1) {
1✔
732
      return Optional.of(constructors[0]);
1✔
733
    }
734
    Optional<Constructor<?>> annotated = Arrays.stream(constructors)
1✔
735
        .filter(x -> x.isAnnotationPresent(AutomapConstructor.class)).reduce((x, y) -> {
1✔
736
          throw new ExecutorException("@AutomapConstructor should be used in only one constructor.");
1✔
737
        });
738
    if (annotated.isPresent()) {
1✔
739
      return annotated;
1✔
740
    } else if (configuration.isArgNameBasedConstructorAutoMapping()) {
1✔
741
      // Finding-best-match type implementation is possible,
742
      // but using @AutomapConstructor seems sufficient.
743
      throw new ExecutorException(MessageFormat.format(
×
744
          "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.",
745
          resultType.getName()));
×
746
    } else {
747
      return Arrays.stream(constructors).filter(x -> findUsableConstructorByArgTypes(x, rsw.getJdbcTypes())).findAny();
1✔
748
    }
749
  }
750

751
  private boolean findUsableConstructorByArgTypes(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) {
752
    final Class<?>[] parameterTypes = constructor.getParameterTypes();
1✔
753
    if (parameterTypes.length != jdbcTypes.size()) {
1✔
754
      return false;
1✔
755
    }
756
    for (int i = 0; i < parameterTypes.length; i++) {
1✔
757
      if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) {
1✔
758
        return false;
×
759
      }
760
    }
761
    return true;
1✔
762
  }
763

764
  private Object applyConstructorAutomapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
765
      Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor)
766
      throws SQLException {
767
    boolean foundValues = false;
1✔
768
    if (configuration.isArgNameBasedConstructorAutoMapping()) {
1✔
769
      foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, resultType,
1✔
770
          constructorArgTypes, constructorArgs, constructor, foundValues);
771
    } else {
772
      foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor,
1✔
773
          foundValues);
774
    }
775
    return foundValues || configuration.isReturnInstanceForEmptyRow()
1✔
776
        ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
1✔
777
  }
778

779
  private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,
780
      List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {
781
    for (int i = 0; i < constructor.getParameterTypes().length; i++) {
1✔
782
      Class<?> parameterType = constructor.getParameterTypes()[i];
1✔
783
      String columnName = rsw.getColumnNames().get(i);
1✔
784
      TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
1✔
785
      Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
786
      constructorArgTypes.add(parameterType);
1✔
787
      constructorArgs.add(value);
1✔
788
      foundValues = value != null || foundValues;
1✔
789
    }
790
    return foundValues;
1✔
791
  }
792

793
  private boolean applyArgNameBasedConstructorAutoMapping(ResultSetWrapper rsw, ResultMap resultMap,
794
      String columnPrefix, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
795
      Constructor<?> constructor, boolean foundValues) throws SQLException {
796
    List<String> missingArgs = null;
1✔
797
    Parameter[] params = constructor.getParameters();
1✔
798
    for (Parameter param : params) {
1✔
799
      boolean columnNotFound = true;
1✔
800
      Param paramAnno = param.getAnnotation(Param.class);
1✔
801
      String paramName = paramAnno == null ? param.getName() : paramAnno.value();
1✔
802
      for (String columnName : rsw.getColumnNames()) {
1✔
803
        if (columnMatchesParam(columnName, paramName, columnPrefix)) {
1✔
804
          Class<?> paramType = param.getType();
1✔
805
          TypeHandler<?> typeHandler = rsw.getTypeHandler(paramType, columnName);
1✔
806
          Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
807
          constructorArgTypes.add(paramType);
1✔
808
          constructorArgs.add(value);
1✔
809
          final String mapKey = resultMap.getId() + ":" + columnPrefix;
1✔
810
          if (!autoMappingsCache.containsKey(mapKey)) {
1✔
811
            MapUtil.computeIfAbsent(constructorAutoMappingColumns, mapKey, k -> new ArrayList<>()).add(columnName);
1✔
812
          }
813
          columnNotFound = false;
1✔
814
          foundValues = value != null || foundValues;
1✔
815
        }
816
      }
1✔
817
      if (columnNotFound) {
1✔
818
        if (missingArgs == null) {
1✔
819
          missingArgs = new ArrayList<>();
1✔
820
        }
821
        missingArgs.add(paramName);
1✔
822
      }
823
    }
824
    if (foundValues && constructorArgs.size() < params.length) {
1✔
825
      throw new ExecutorException(MessageFormat.format(
1✔
826
          "Constructor auto-mapping of ''{1}'' failed " + "because ''{0}'' were not found in the result set; "
827
              + "Available columns are ''{2}'' and mapUnderscoreToCamelCase is ''{3}''.",
828
          missingArgs, constructor, rsw.getColumnNames(), configuration.isMapUnderscoreToCamelCase()));
1✔
829
    }
830
    return foundValues;
1✔
831
  }
832

833
  private boolean columnMatchesParam(String columnName, String paramName, String columnPrefix) {
834
    if (columnPrefix != null) {
1✔
835
      if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
836
        return false;
1✔
837
      }
838
      columnName = columnName.substring(columnPrefix.length());
1✔
839
    }
840
    return paramName
1✔
841
        .equalsIgnoreCase(configuration.isMapUnderscoreToCamelCase() ? columnName.replace("_", "") : columnName);
1✔
842
  }
843

844
  private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
845
      throws SQLException {
846
    final Class<?> resultType = resultMap.getType();
1✔
847
    final String columnName;
848
    if (!resultMap.getResultMappings().isEmpty()) {
1✔
849
      final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
1✔
850
      final ResultMapping mapping = resultMappingList.get(0);
1✔
851
      columnName = prependPrefix(mapping.getColumn(), columnPrefix);
1✔
852
    } else {
1✔
853
      columnName = rsw.getColumnNames().get(0);
1✔
854
    }
855
    final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
1✔
856
    return typeHandler.getResult(rsw.getResultSet(), columnName);
1✔
857
  }
858

859
  //
860
  // NESTED QUERY
861
  //
862

863
  private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)
864
      throws SQLException {
865
    final String nestedQueryId = constructorMapping.getNestedQueryId();
1✔
866
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
1✔
867
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
1✔
868
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping,
1✔
869
        nestedQueryParameterType, columnPrefix);
870
    Object value = null;
1✔
871
    if (nestedQueryParameterObject != null) {
1✔
872
      final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
1✔
873
      final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
1✔
874
          nestedBoundSql);
875
      final Class<?> targetType = constructorMapping.getJavaType();
1✔
876
      final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
1✔
877
          nestedQueryParameterObject, targetType, key, nestedBoundSql);
878
      value = resultLoader.loadResult();
1✔
879
    }
880
    return value;
1✔
881
  }
882

883
  private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
884
      ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
885
    final String nestedQueryId = propertyMapping.getNestedQueryId();
1✔
886
    final String property = propertyMapping.getProperty();
1✔
887
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
1✔
888
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
1✔
889
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping,
1✔
890
        nestedQueryParameterType, columnPrefix);
891
    Object value = null;
1✔
892
    if (nestedQueryParameterObject != null) {
1✔
893
      final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
1✔
894
      final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
1✔
895
          nestedBoundSql);
896
      final Class<?> targetType = propertyMapping.getJavaType();
1✔
897
      if (executor.isCached(nestedQuery, key)) {
1✔
898
        executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
1✔
899
        value = DEFERRED;
1✔
900
      } else {
901
        final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
1✔
902
            nestedQueryParameterObject, targetType, key, nestedBoundSql);
903
        if (propertyMapping.isLazy()) {
1✔
904
          lazyLoader.addLoader(property, metaResultObject, resultLoader);
1✔
905
          value = DEFERRED;
1✔
906
        } else {
907
          value = resultLoader.loadResult();
1✔
908
        }
909
      }
910
    }
911
    return value;
1✔
912
  }
913

914
  private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
915
      String columnPrefix) throws SQLException {
916
    if (resultMapping.isCompositeResult()) {
1✔
917
      return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix);
1✔
918
    } else {
919
      return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix);
1✔
920
    }
921
  }
922

923
  private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
924
      String columnPrefix) throws SQLException {
925
    final TypeHandler<?> typeHandler;
926
    if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
1✔
927
      typeHandler = typeHandlerRegistry.getTypeHandler(parameterType);
1✔
928
    } else {
929
      typeHandler = typeHandlerRegistry.getUnknownTypeHandler();
1✔
930
    }
931
    return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
1✔
932
  }
933

934
  private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
935
      String columnPrefix) throws SQLException {
936
    final Object parameterObject = instantiateParameterObject(parameterType);
1✔
937
    final MetaObject metaObject = configuration.newMetaObject(parameterObject);
1✔
938
    boolean foundValues = false;
1✔
939
    for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
1✔
940
      final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty());
1✔
941
      final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType);
1✔
942
      final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix));
1✔
943
      // issue #353 & #560 do not execute nested query if key is null
944
      if (propValue != null) {
1✔
945
        metaObject.setValue(innerResultMapping.getProperty(), propValue);
1✔
946
        foundValues = true;
1✔
947
      }
948
    }
1✔
949
    return foundValues ? parameterObject : null;
1✔
950
  }
951

952
  private Object instantiateParameterObject(Class<?> parameterType) {
953
    if (parameterType == null) {
1✔
954
      return new HashMap<>();
1✔
955
    } else if (ParamMap.class.equals(parameterType)) {
1✔
956
      return new HashMap<>(); // issue #649
1✔
957
    } else {
958
      return objectFactory.create(parameterType);
1✔
959
    }
960
  }
961

962
  //
963
  // DISCRIMINATOR
964
  //
965

966
  public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)
967
      throws SQLException {
968
    Set<String> pastDiscriminators = new HashSet<>();
1✔
969
    Discriminator discriminator = resultMap.getDiscriminator();
1✔
970
    while (discriminator != null) {
1✔
971
      final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
1✔
972
      final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
1✔
973
      if (configuration.hasResultMap(discriminatedMapId)) {
1✔
974
        resultMap = configuration.getResultMap(discriminatedMapId);
1✔
975
        Discriminator lastDiscriminator = discriminator;
1✔
976
        discriminator = resultMap.getDiscriminator();
1✔
977
        if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
1✔
978
          break;
1✔
979
        }
980
      } else {
981
        break;
982
      }
983
    }
1✔
984
    return resultMap;
1✔
985
  }
986

987
  private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix)
988
      throws SQLException {
989
    final ResultMapping resultMapping = discriminator.getResultMapping();
1✔
990
    final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
1✔
991
    return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
1✔
992
  }
993

994
  private String prependPrefix(String columnName, String prefix) {
995
    if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
1✔
996
      return columnName;
1✔
997
    }
998
    return prefix + columnName;
1✔
999
  }
1000

1001
  //
1002
  // HANDLE NESTED RESULT MAPS
1003
  //
1004

1005
  private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap,
1006
      ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
1007
    final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
1✔
1008
    ResultSet resultSet = rsw.getResultSet();
1✔
1009
    skipRows(resultSet, rowBounds);
1✔
1010
    Object rowValue = previousRowValue;
1✔
1011
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
1✔
1012
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
1✔
1013
      final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
1✔
1014
      Object partialObject = nestedResultObjects.get(rowKey);
1✔
1015
      // issue #577 && #542
1016
      if (mappedStatement.isResultOrdered()) {
1✔
1017
        if (partialObject == null && rowValue != null) {
1✔
1018
          nestedResultObjects.clear();
1✔
1019
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1020
        }
1021
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1022
      } else {
1023
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1✔
1024
        if (partialObject == null) {
1✔
1025
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1026
        }
1027
      }
1028
    }
1✔
1029
    if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
1✔
1030
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1✔
1031
      previousRowValue = null;
1✔
1032
    } else if (rowValue != null) {
1✔
1033
      previousRowValue = rowValue;
1✔
1034
    }
1035
  }
1✔
1036

1037
  //
1038
  // NESTED RESULT MAP (JOIN MAPPING)
1039
  //
1040

1041
  private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
1042
      String parentPrefix, CacheKey parentRowKey, boolean newObject) {
1043
    boolean foundValues = false;
1✔
1044
    for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
1✔
1045
      final String nestedResultMapId = resultMapping.getNestedResultMapId();
1✔
1046
      if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
1✔
1047
        try {
1048
          final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
1✔
1049
          final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
1✔
1050
          if (resultMapping.getColumnPrefix() == null) {
1✔
1051
            // try to fill circular reference only when columnPrefix
1052
            // is not specified for the nested result map (issue #215)
1053
            Object ancestorObject = ancestorObjects.get(nestedResultMapId);
1✔
1054
            if (ancestorObject != null) {
1✔
1055
              if (newObject) {
1✔
1056
                linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
1✔
1057
              }
1058
              continue;
1✔
1059
            }
1060
          }
1061
          final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
1✔
1062
          final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1✔
1063
          Object rowValue = nestedResultObjects.get(combinedKey);
1✔
1064
          boolean knownValue = rowValue != null;
1✔
1065
          instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
1✔
1066
          if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
1✔
1067
            rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
1✔
1068
            if (rowValue != null && !knownValue) {
1✔
1069
              linkObjects(metaObject, resultMapping, rowValue);
1✔
1070
              foundValues = true;
1✔
1071
            }
1072
          }
1073
        } catch (SQLException e) {
×
1074
          throw new ExecutorException(
×
1075
              "Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
×
1076
        }
1✔
1077
      }
1078
    }
1✔
1079
    return foundValues;
1✔
1080
  }
1081

1082
  private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) {
1083
    final StringBuilder columnPrefixBuilder = new StringBuilder();
1✔
1084
    if (parentPrefix != null) {
1✔
1085
      columnPrefixBuilder.append(parentPrefix);
1✔
1086
    }
1087
    if (resultMapping.getColumnPrefix() != null) {
1✔
1088
      columnPrefixBuilder.append(resultMapping.getColumnPrefix());
1✔
1089
    }
1090
    return columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH);
1✔
1091
  }
1092

1093
  private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSetWrapper rsw)
1094
      throws SQLException {
1095
    Set<String> notNullColumns = resultMapping.getNotNullColumns();
1✔
1096
    if (notNullColumns != null && !notNullColumns.isEmpty()) {
1✔
1097
      ResultSet rs = rsw.getResultSet();
1✔
1098
      for (String column : notNullColumns) {
1✔
1099
        rs.getObject(prependPrefix(column, columnPrefix));
1✔
1100
        if (!rs.wasNull()) {
1✔
1101
          return true;
1✔
1102
        }
1103
      }
1✔
1104
      return false;
1✔
1105
    } else if (columnPrefix != null) {
1✔
1106
      for (String columnName : rsw.getColumnNames()) {
1✔
1107
        if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix.toUpperCase(Locale.ENGLISH))) {
1✔
1108
          return true;
1✔
1109
        }
1110
      }
1✔
1111
      return false;
1✔
1112
    }
1113
    return true;
1✔
1114
  }
1115

1116
  private ResultMap getNestedResultMap(ResultSet rs, String nestedResultMapId, String columnPrefix)
1117
      throws SQLException {
1118
    ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId);
1✔
1119
    return resolveDiscriminatedResultMap(rs, nestedResultMap, columnPrefix);
1✔
1120
  }
1121

1122
  //
1123
  // UNIQUE RESULT KEY
1124
  //
1125

1126
  private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
1127
    final CacheKey cacheKey = new CacheKey();
1✔
1128
    cacheKey.update(resultMap.getId());
1✔
1129
    List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
1✔
1130
    if (resultMappings.isEmpty()) {
1✔
1131
      if (Map.class.isAssignableFrom(resultMap.getType())) {
1✔
1132
        createRowKeyForMap(rsw, cacheKey);
×
1133
      } else {
1134
        createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
1✔
1135
      }
1136
    } else {
1137
      createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
1✔
1138
    }
1139
    if (cacheKey.getUpdateCount() < 2) {
1✔
1140
      return CacheKey.NULL_CACHE_KEY;
1✔
1141
    }
1142
    return cacheKey;
1✔
1143
  }
1144

1145
  private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) {
1146
    if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) {
1✔
1147
      CacheKey combinedKey;
1148
      try {
1149
        combinedKey = rowKey.clone();
1✔
1150
      } catch (CloneNotSupportedException e) {
×
1151
        throw new ExecutorException("Error cloning cache key.  Cause: " + e, e);
×
1152
      }
1✔
1153
      combinedKey.update(parentRowKey);
1✔
1154
      return combinedKey;
1✔
1155
    }
1156
    return CacheKey.NULL_CACHE_KEY;
1✔
1157
  }
1158

1159
  private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {
1160
    List<ResultMapping> resultMappings = resultMap.getIdResultMappings();
1✔
1161
    if (resultMappings.isEmpty()) {
1✔
1162
      resultMappings = resultMap.getPropertyResultMappings();
1✔
1163
    }
1164
    return resultMappings;
1✔
1165
  }
1166

1167
  private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1168
      List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {
1169
    for (ResultMapping resultMapping : resultMappings) {
1✔
1170
      if (resultMapping.isSimple()) {
1✔
1171
        final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
1✔
1172
        final TypeHandler<?> th = resultMapping.getTypeHandler();
1✔
1173
        List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
1✔
1174
        // Issue #114
1175
        if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
1✔
1176
          final Object value = th.getResult(rsw.getResultSet(), column);
1✔
1177
          if (value != null || configuration.isReturnInstanceForEmptyRow()) {
1✔
1178
            cacheKey.update(column);
1✔
1179
            cacheKey.update(value);
1✔
1180
          }
1181
        }
1182
      }
1183
    }
1✔
1184
  }
1✔
1185

1186
  private void createRowKeyForUnmappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1187
      String columnPrefix) throws SQLException {
1188
    final MetaClass metaType = MetaClass.forClass(resultMap.getType(), reflectorFactory);
1✔
1189
    List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
1✔
1190
    for (String column : unmappedColumnNames) {
1✔
1191
      String property = column;
1✔
1192
      if (columnPrefix != null && !columnPrefix.isEmpty()) {
1✔
1193
        // When columnPrefix is specified, ignore columns without the prefix.
1194
        if (column.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1✔
1195
          property = column.substring(columnPrefix.length());
1✔
1196
        } else {
1197
          continue;
1198
        }
1199
      }
1200
      if (metaType.findProperty(property, configuration.isMapUnderscoreToCamelCase()) != null) {
1✔
1201
        String value = rsw.getResultSet().getString(column);
1✔
1202
        if (value != null) {
1✔
1203
          cacheKey.update(column);
1✔
1204
          cacheKey.update(value);
1✔
1205
        }
1206
      }
1207
    }
1✔
1208
  }
1✔
1209

1210
  private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException {
1211
    List<String> columnNames = rsw.getColumnNames();
×
1212
    for (String columnName : columnNames) {
×
1213
      final String value = rsw.getResultSet().getString(columnName);
×
1214
      if (value != null) {
×
1215
        cacheKey.update(columnName);
×
1216
        cacheKey.update(value);
×
1217
      }
1218
    }
×
1219
  }
×
1220

1221
  private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
1222
    final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1✔
1223
    if (collectionProperty != null) {
1✔
1224
      final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1✔
1225
      targetMetaObject.add(rowValue);
1✔
1226
    } else {
1✔
1227
      metaObject.setValue(resultMapping.getProperty(), rowValue);
1✔
1228
    }
1229
  }
1✔
1230

1231
  private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
1232
    final String propertyName = resultMapping.getProperty();
1✔
1233
    Object propertyValue = metaObject.getValue(propertyName);
1✔
1234
    if (propertyValue == null) {
1✔
1235
      Class<?> type = resultMapping.getJavaType();
1✔
1236
      if (type == null) {
1✔
1237
        type = metaObject.getSetterType(propertyName);
1✔
1238
      }
1239
      try {
1240
        if (objectFactory.isCollection(type)) {
1✔
1241
          propertyValue = objectFactory.create(type);
1✔
1242
          metaObject.setValue(propertyName, propertyValue);
1✔
1243
          return propertyValue;
1✔
1244
        }
1245
      } catch (Exception e) {
×
1246
        throw new ExecutorException(
×
1247
            "Error instantiating collection property for result '" + resultMapping.getProperty() + "'.  Cause: " + e,
×
1248
            e);
1249
      }
1✔
1250
    } else if (objectFactory.isCollection(propertyValue.getClass())) {
1✔
1251
      return propertyValue;
1✔
1252
    }
1253
    return null;
1✔
1254
  }
1255

1256
  private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
1257
    if (rsw.getColumnNames().size() == 1) {
1✔
1258
      return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0)));
1✔
1259
    }
1260
    return typeHandlerRegistry.hasTypeHandler(resultType);
1✔
1261
  }
1262

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