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

mybatis / mybatis-3 / 2604

03 Jan 2025 10:00AM UTC coverage: 87.524% (+0.3%) from 87.177%
2604

Pull #3146

github

web-flow
Merge 60c1f5fea into 8ac3920af
Pull Request #3146: Shared ambiguity instance

3633 of 4401 branches covered (82.55%)

4 of 4 new or added lines in 1 file covered. (100.0%)

254 existing lines in 22 files now uncovered.

9569 of 10933 relevant lines covered (87.52%)

0.88 hits per line

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

95.4
/src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.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.builder;
17

18
import java.sql.ResultSet;
19
import java.util.ArrayList;
20
import java.util.Collections;
21
import java.util.HashMap;
22
import java.util.HashSet;
23
import java.util.List;
24
import java.util.Map;
25
import java.util.Properties;
26
import java.util.Set;
27
import java.util.StringTokenizer;
28

29
import org.apache.ibatis.cache.Cache;
30
import org.apache.ibatis.cache.decorators.LruCache;
31
import org.apache.ibatis.cache.impl.PerpetualCache;
32
import org.apache.ibatis.executor.ErrorContext;
33
import org.apache.ibatis.executor.keygen.KeyGenerator;
34
import org.apache.ibatis.mapping.CacheBuilder;
35
import org.apache.ibatis.mapping.Discriminator;
36
import org.apache.ibatis.mapping.MappedStatement;
37
import org.apache.ibatis.mapping.ParameterMap;
38
import org.apache.ibatis.mapping.ParameterMapping;
39
import org.apache.ibatis.mapping.ParameterMode;
40
import org.apache.ibatis.mapping.ResultFlag;
41
import org.apache.ibatis.mapping.ResultMap;
42
import org.apache.ibatis.mapping.ResultMapping;
43
import org.apache.ibatis.mapping.ResultSetType;
44
import org.apache.ibatis.mapping.SqlCommandType;
45
import org.apache.ibatis.mapping.SqlSource;
46
import org.apache.ibatis.mapping.StatementType;
47
import org.apache.ibatis.reflection.MetaClass;
48
import org.apache.ibatis.scripting.LanguageDriver;
49
import org.apache.ibatis.session.Configuration;
50
import org.apache.ibatis.type.JdbcType;
51
import org.apache.ibatis.type.TypeHandler;
52

53
/**
54
 * @author Clinton Begin
55
 */
56
public class MapperBuilderAssistant extends BaseBuilder {
57

58
  private String currentNamespace;
59
  private final String resource;
60
  private Cache currentCache;
61
  private boolean unresolvedCacheRef; // issue #676
62

63
  public MapperBuilderAssistant(Configuration configuration, String resource) {
64
    super(configuration);
1✔
65
    ErrorContext.instance().resource(resource);
1✔
66
    this.resource = resource;
1✔
67
  }
1✔
68

69
  public String getCurrentNamespace() {
70
    return currentNamespace;
1✔
71
  }
72

73
  public void setCurrentNamespace(String currentNamespace) {
74
    if (currentNamespace == null) {
1✔
75
      throw new BuilderException("The mapper element requires a namespace attribute to be specified.");
1✔
76
    }
77

78
    if (this.currentNamespace != null && !this.currentNamespace.equals(currentNamespace)) {
1✔
79
      throw new BuilderException(
1✔
80
          "Wrong namespace. Expected '" + this.currentNamespace + "' but found '" + currentNamespace + "'.");
81
    }
82

83
    this.currentNamespace = currentNamespace;
1✔
84
  }
1✔
85

86
  public String applyCurrentNamespace(String base, boolean isReference) {
87
    if (base == null) {
1✔
88
      return null;
1✔
89
    }
90
    if (isReference) {
1✔
91
      // is it qualified with any namespace yet?
92
      if (base.contains(".")) {
1✔
93
        return base;
1✔
94
      }
95
    } else {
96
      // is it qualified with this namespace yet?
97
      if (base.startsWith(currentNamespace + ".")) {
1✔
98
        return base;
1✔
99
      }
100
      if (base.contains(".")) {
1✔
101
        throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
1✔
102
      }
103
    }
104
    return currentNamespace + "." + base;
1✔
105
  }
106

107
  public Cache useCacheRef(String namespace) {
108
    if (namespace == null) {
1✔
109
      throw new BuilderException("cache-ref element requires a namespace attribute.");
1✔
110
    }
111
    try {
112
      unresolvedCacheRef = true;
1✔
113
      Cache cache = configuration.getCache(namespace);
1✔
114
      if (cache == null) {
1!
UNCOV
115
        throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
×
116
      }
117
      currentCache = cache;
1✔
118
      unresolvedCacheRef = false;
1✔
119
      return cache;
1✔
120
    } catch (IllegalArgumentException e) {
1✔
121
      throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
1✔
122
    }
123
  }
124

125
  public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval,
126
      Integer size, boolean readWrite, boolean blocking, Properties props) {
127
    Cache cache = new CacheBuilder(currentNamespace).implementation(valueOrDefault(typeClass, PerpetualCache.class))
1✔
128
        .addDecorator(valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval).size(size)
1✔
129
        .readWrite(readWrite).blocking(blocking).properties(props).build();
1✔
130
    configuration.addCache(cache);
1✔
131
    currentCache = cache;
1✔
132
    return cache;
1✔
133
  }
134

135
  public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) {
136
    id = applyCurrentNamespace(id, false);
1✔
137
    ParameterMap parameterMap = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings).build();
1✔
138
    configuration.addParameterMap(parameterMap);
1✔
139
    return parameterMap;
1✔
140
  }
141

142
  public ParameterMapping buildParameterMapping(Class<?> parameterType, String property, Class<?> javaType,
143
      JdbcType jdbcType, String resultMap, ParameterMode parameterMode, Class<? extends TypeHandler<?>> typeHandler,
144
      Integer numericScale) {
145
    resultMap = applyCurrentNamespace(resultMap, true);
1✔
146

147
    // Class parameterType = parameterMapBuilder.type();
148
    Class<?> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType);
1✔
149
    TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
1✔
150

151
    return new ParameterMapping.Builder(configuration, property, javaTypeClass).jdbcType(jdbcType)
1✔
152
        .resultMapId(resultMap).mode(parameterMode).numericScale(numericScale).typeHandler(typeHandlerInstance).build();
1✔
153
  }
154

155
  public ResultMap addResultMap(String id, Class<?> type, String extend, Discriminator discriminator,
156
      List<ResultMapping> resultMappings, Boolean autoMapping) {
157
    id = applyCurrentNamespace(id, false);
1✔
158
    extend = applyCurrentNamespace(extend, true);
1✔
159

160
    if (extend != null) {
1✔
161
      if (!configuration.hasResultMap(extend)) {
1✔
162
        throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
1✔
163
      }
164
      ResultMap resultMap = configuration.getResultMap(extend);
1✔
165
      List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
1✔
166
      extendedResultMappings.removeAll(resultMappings);
1✔
167
      // Remove parent constructor if this resultMap declares a constructor.
168
      boolean declaresConstructor = false;
1✔
169
      for (ResultMapping resultMapping : resultMappings) {
1✔
170
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
1✔
171
          declaresConstructor = true;
1✔
172
          break;
1✔
173
        }
174
      }
1✔
175
      if (declaresConstructor) {
1✔
176
        extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
1✔
177
      }
178
      resultMappings.addAll(extendedResultMappings);
1✔
179
    }
180
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
1✔
181
        .discriminator(discriminator).build();
1✔
182
    configuration.addResultMap(resultMap);
1✔
183
    return resultMap;
1✔
184
  }
185

186
  public Discriminator buildDiscriminator(Class<?> resultType, String column, Class<?> javaType, JdbcType jdbcType,
187
      Class<? extends TypeHandler<?>> typeHandler, Map<String, String> discriminatorMap) {
188
    ResultMapping resultMapping = buildResultMapping(resultType, null, column, javaType, jdbcType, null, null, null,
1✔
189
        null, typeHandler, new ArrayList<>(), null, null, false);
190
    Map<String, String> namespaceDiscriminatorMap = new HashMap<>();
1✔
191
    for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
1✔
192
      String resultMap = e.getValue();
1✔
193
      resultMap = applyCurrentNamespace(resultMap, true);
1✔
194
      namespaceDiscriminatorMap.put(e.getKey(), resultMap);
1✔
195
    }
1✔
196
    return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
1✔
197
  }
198

199
  public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
200
      SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
201
      String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
202
      boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
203
      LanguageDriver lang, String resultSets, boolean dirtySelect) {
204

205
    if (unresolvedCacheRef) {
1✔
206
      throw new IncompleteElementException("Cache-ref not yet resolved");
1✔
207
    }
208

209
    id = applyCurrentNamespace(id, false);
1✔
210

211
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
1✔
212
        .resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType)
1✔
213
        .keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang)
1✔
214
        .resultOrdered(resultOrdered).resultSets(resultSets)
1✔
215
        .resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType)
1✔
216
        .flushCacheRequired(flushCache).useCache(useCache).cache(currentCache).dirtySelect(dirtySelect);
1✔
217

218
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
1✔
219
    if (statementParameterMap != null) {
1✔
220
      statementBuilder.parameterMap(statementParameterMap);
1✔
221
    }
222

223
    MappedStatement statement = statementBuilder.build();
1✔
224
    configuration.addMappedStatement(statement);
1✔
225
    return statement;
1✔
226
  }
227

228
  /**
229
   * Backward compatibility signature 'addMappedStatement'.
230
   *
231
   * @param id
232
   *          the id
233
   * @param sqlSource
234
   *          the sql source
235
   * @param statementType
236
   *          the statement type
237
   * @param sqlCommandType
238
   *          the sql command type
239
   * @param fetchSize
240
   *          the fetch size
241
   * @param timeout
242
   *          the timeout
243
   * @param parameterMap
244
   *          the parameter map
245
   * @param parameterType
246
   *          the parameter type
247
   * @param resultMap
248
   *          the result map
249
   * @param resultType
250
   *          the result type
251
   * @param resultSetType
252
   *          the result set type
253
   * @param flushCache
254
   *          the flush cache
255
   * @param useCache
256
   *          the use cache
257
   * @param resultOrdered
258
   *          the result ordered
259
   * @param keyGenerator
260
   *          the key generator
261
   * @param keyProperty
262
   *          the key property
263
   * @param keyColumn
264
   *          the key column
265
   * @param databaseId
266
   *          the database id
267
   * @param lang
268
   *          the lang
269
   *
270
   * @return the mapped statement
271
   */
272
  public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
273
      SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
274
      String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
275
      boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
276
      LanguageDriver lang, String resultSets) {
UNCOV
277
    return addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
×
278
        parameterType, resultMap, resultType, resultSetType, flushCache, useCache, resultOrdered, keyGenerator,
279
        keyProperty, keyColumn, databaseId, lang, null, false);
280
  }
281

282
  public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
283
      SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
284
      String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
285
      boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
286
      LanguageDriver lang) {
UNCOV
287
    return addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
×
288
        parameterType, resultMap, resultType, resultSetType, flushCache, useCache, resultOrdered, keyGenerator,
289
        keyProperty, keyColumn, databaseId, lang, null);
290
  }
291

292
  private <T> T valueOrDefault(T value, T defaultValue) {
293
    return value == null ? defaultValue : value;
1!
294
  }
295

296
  private ParameterMap getStatementParameterMap(String parameterMapName, Class<?> parameterTypeClass,
297
      String statementId) {
298
    parameterMapName = applyCurrentNamespace(parameterMapName, true);
1✔
299
    ParameterMap parameterMap = null;
1✔
300
    if (parameterMapName != null) {
1✔
301
      try {
302
        parameterMap = configuration.getParameterMap(parameterMapName);
1✔
303
      } catch (IllegalArgumentException e) {
1✔
304
        throw new IncompleteElementException("Could not find parameter map " + parameterMapName, e);
1✔
305
      }
1✔
306
    } else if (parameterTypeClass != null) {
1✔
307
      List<ParameterMapping> parameterMappings = new ArrayList<>();
1✔
308
      parameterMap = new ParameterMap.Builder(configuration, statementId + "-Inline", parameterTypeClass,
1✔
309
          parameterMappings).build();
1✔
310
    }
311
    return parameterMap;
1✔
312
  }
313

314
  private List<ResultMap> getStatementResultMaps(String resultMap, Class<?> resultType, String statementId) {
315
    resultMap = applyCurrentNamespace(resultMap, true);
1✔
316

317
    List<ResultMap> resultMaps = new ArrayList<>();
1✔
318
    if (resultMap != null) {
1✔
319
      String[] resultMapNames = resultMap.split(",");
1✔
320
      for (String resultMapName : resultMapNames) {
1✔
321
        try {
322
          resultMaps.add(configuration.getResultMap(resultMapName.trim()));
1✔
323
        } catch (IllegalArgumentException e) {
1✔
324
          throw new IncompleteElementException(
1✔
325
              "Could not find result map '" + resultMapName + "' referenced from '" + statementId + "'", e);
326
        }
1✔
327
      }
328
    } else if (resultType != null) {
1✔
329
      ResultMap inlineResultMap = new ResultMap.Builder(configuration, statementId + "-Inline", resultType,
1✔
330
          new ArrayList<>(), null).build();
1✔
331
      resultMaps.add(inlineResultMap);
1✔
332
    }
333
    return resultMaps;
1✔
334
  }
335

336
  public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, Class<?> javaType,
337
      JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix,
338
      Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags, String resultSet, String foreignColumn,
339
      boolean lazy) {
340
    Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
1✔
341
    TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
1✔
342
    List<ResultMapping> composites;
343
    if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {
1!
344
      composites = Collections.emptyList();
1✔
345
    } else {
346
      composites = parseCompositeColumnName(column);
1✔
347
    }
348
    return new ResultMapping.Builder(configuration, property, column, javaTypeClass).jdbcType(jdbcType)
1✔
349
        .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
1✔
350
        .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true)).resultSet(resultSet)
1✔
351
        .typeHandler(typeHandlerInstance).flags(flags == null ? new ArrayList<>() : flags).composites(composites)
1!
352
        .notNullColumns(parseMultipleColumnNames(notNullColumn)).columnPrefix(columnPrefix).foreignColumn(foreignColumn)
1✔
353
        .lazy(lazy).build();
1✔
354
  }
355

356
  /**
357
   * Backward compatibility signature 'buildResultMapping'.
358
   *
359
   * @param resultType
360
   *          the result type
361
   * @param property
362
   *          the property
363
   * @param column
364
   *          the column
365
   * @param javaType
366
   *          the java type
367
   * @param jdbcType
368
   *          the jdbc type
369
   * @param nestedSelect
370
   *          the nested select
371
   * @param nestedResultMap
372
   *          the nested result map
373
   * @param notNullColumn
374
   *          the not null column
375
   * @param columnPrefix
376
   *          the column prefix
377
   * @param typeHandler
378
   *          the type handler
379
   * @param flags
380
   *          the flags
381
   *
382
   * @return the result mapping
383
   */
384
  public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, Class<?> javaType,
385
      JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix,
386
      Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags) {
387
    return buildResultMapping(resultType, property, column, javaType, jdbcType, nestedSelect, nestedResultMap,
×
UNCOV
388
        notNullColumn, columnPrefix, typeHandler, flags, null, null, configuration.isLazyLoadingEnabled());
×
389
  }
390

391
  /**
392
   * Gets the language driver.
393
   *
394
   * @param langClass
395
   *          the lang class
396
   *
397
   * @return the language driver
398
   *
399
   * @deprecated Use {@link Configuration#getLanguageDriver(Class)}
400
   */
401
  @Deprecated
402
  public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
UNCOV
403
    return configuration.getLanguageDriver(langClass);
×
404
  }
405

406
  private Set<String> parseMultipleColumnNames(String columnName) {
407
    Set<String> columns = new HashSet<>();
1✔
408
    if (columnName != null) {
1✔
409
      if (columnName.indexOf(',') > -1) {
1✔
410
        StringTokenizer parser = new StringTokenizer(columnName, "{}, ", false);
1✔
411
        while (parser.hasMoreTokens()) {
1✔
412
          String column = parser.nextToken();
1✔
413
          columns.add(column);
1✔
414
        }
1✔
415
      } else {
1✔
416
        columns.add(columnName);
1✔
417
      }
418
    }
419
    return columns;
1✔
420
  }
421

422
  private List<ResultMapping> parseCompositeColumnName(String columnName) {
423
    List<ResultMapping> composites = new ArrayList<>();
1✔
424
    if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
1!
425
      StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
1✔
426
      while (parser.hasMoreTokens()) {
1✔
427
        String property = parser.nextToken();
1✔
428
        String column = parser.nextToken();
1✔
429
        ResultMapping complexResultMapping = new ResultMapping.Builder(configuration, property, column,
1✔
430
            configuration.getTypeHandlerRegistry().getUnknownTypeHandler()).build();
1✔
431
        composites.add(complexResultMapping);
1✔
432
      }
1✔
433
    }
434
    return composites;
1✔
435
  }
436

437
  private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
438
    if (javaType == null && property != null) {
1✔
439
      try {
440
        MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
1✔
441
        javaType = metaResultType.getSetterType(property);
1✔
442
      } catch (Exception e) {
1✔
443
        // ignore, following null check statement will deal with the situation
444
      }
1✔
445
    }
446
    if (javaType == null) {
1✔
447
      javaType = Object.class;
1✔
448
    }
449
    return javaType;
1✔
450
  }
451

452
  private Class<?> resolveParameterJavaType(Class<?> resultType, String property, Class<?> javaType,
453
      JdbcType jdbcType) {
454
    if (javaType == null) {
1!
455
      if (JdbcType.CURSOR.equals(jdbcType)) {
1!
UNCOV
456
        javaType = ResultSet.class;
×
457
      } else if (Map.class.isAssignableFrom(resultType)) {
1✔
458
        javaType = Object.class;
1✔
459
      } else {
460
        MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
1✔
461
        javaType = metaResultType.getGetterType(property);
1✔
462
      }
463
    }
464
    if (javaType == null) {
1!
UNCOV
465
      javaType = Object.class;
×
466
    }
467
    return javaType;
1✔
468
  }
469

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