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

mybatis / mybatis-3 / #3393

25 Dec 2023 01:42AM UTC coverage: 86.665% (+0.05%) from 86.619%
#3393

Pull #2971

github

web-flow
Merge 9b99f44b6 into 01225f508
Pull Request #2971: docs: add dynamic sql example to @Select

3498 of 4275 branches covered (0.0%)

9294 of 10724 relevant lines covered (86.67%)

0.87 hits per line

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

96.86
/src/main/java/org/apache/ibatis/builder/xml/XMLMapperBuilder.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.builder.xml;
17

18
import java.io.InputStream;
19
import java.io.Reader;
20
import java.util.ArrayList;
21
import java.util.Arrays;
22
import java.util.Collection;
23
import java.util.Collections;
24
import java.util.HashMap;
25
import java.util.Iterator;
26
import java.util.List;
27
import java.util.Map;
28
import java.util.Properties;
29

30
import org.apache.ibatis.builder.BaseBuilder;
31
import org.apache.ibatis.builder.BuilderException;
32
import org.apache.ibatis.builder.CacheRefResolver;
33
import org.apache.ibatis.builder.IncompleteElementException;
34
import org.apache.ibatis.builder.MapperBuilderAssistant;
35
import org.apache.ibatis.builder.ResultMapResolver;
36
import org.apache.ibatis.cache.Cache;
37
import org.apache.ibatis.executor.ErrorContext;
38
import org.apache.ibatis.io.Resources;
39
import org.apache.ibatis.mapping.Discriminator;
40
import org.apache.ibatis.mapping.ParameterMapping;
41
import org.apache.ibatis.mapping.ParameterMode;
42
import org.apache.ibatis.mapping.ResultFlag;
43
import org.apache.ibatis.mapping.ResultMap;
44
import org.apache.ibatis.mapping.ResultMapping;
45
import org.apache.ibatis.parsing.XNode;
46
import org.apache.ibatis.parsing.XPathParser;
47
import org.apache.ibatis.reflection.MetaClass;
48
import org.apache.ibatis.session.Configuration;
49
import org.apache.ibatis.type.JdbcType;
50
import org.apache.ibatis.type.TypeHandler;
51

52
/**
53
 * @author Clinton Begin
54
 * @author Kazuki Shimizu
55
 */
56
public class XMLMapperBuilder extends BaseBuilder {
57

58
  private final XPathParser parser;
59
  private final MapperBuilderAssistant builderAssistant;
60
  private final Map<String, XNode> sqlFragments;
61
  private final String resource;
62

63
  @Deprecated
64
  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments,
65
      String namespace) {
66
    this(reader, configuration, resource, sqlFragments);
×
67
    this.builderAssistant.setCurrentNamespace(namespace);
×
68
  }
×
69

70
  @Deprecated
71
  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource,
72
      Map<String, XNode> sqlFragments) {
73
    this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration,
×
74
        resource, sqlFragments);
75
  }
×
76

77
  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
78
      Map<String, XNode> sqlFragments, String namespace) {
79
    this(inputStream, configuration, resource, sqlFragments);
1✔
80
    this.builderAssistant.setCurrentNamespace(namespace);
1✔
81
  }
1✔
82

83
  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
84
      Map<String, XNode> sqlFragments) {
85
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration,
1✔
86
        resource, sqlFragments);
87
  }
1✔
88

89
  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource,
90
      Map<String, XNode> sqlFragments) {
91
    super(configuration);
1✔
92
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
1✔
93
    this.parser = parser;
1✔
94
    this.sqlFragments = sqlFragments;
1✔
95
    this.resource = resource;
1✔
96
  }
1✔
97

98
  public void parse() {
99
    if (!configuration.isResourceLoaded(resource)) {
1!
100
      configurationElement(parser.evalNode("/mapper"));
1✔
101
      configuration.addLoadedResource(resource);
1✔
102
      bindMapperForNamespace();
1✔
103
    }
104
    parsePendingResultMaps();
1✔
105
    parsePendingCacheRefs();
1✔
106
    parsePendingStatements();
1✔
107
  }
1✔
108

109
  public XNode getSqlFragment(String refid) {
110
    return sqlFragments.get(refid);
×
111
  }
112

113
  private void configurationElement(XNode context) {
114
    try {
115
      String namespace = context.getStringAttribute("namespace");
1✔
116
      if (namespace == null || namespace.isEmpty()) {
1✔
117
        throw new BuilderException("Mapper's namespace cannot be empty");
1✔
118
      }
119
      builderAssistant.setCurrentNamespace(namespace);
1✔
120
      cacheRefElement(context.evalNode("cache-ref"));
1✔
121
      cacheElement(context.evalNode("cache"));
1✔
122
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
1✔
123
      resultMapElements(context.evalNodes("/mapper/resultMap"));
1✔
124
      sqlElement(context.evalNodes("/mapper/sql"));
1✔
125
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
1✔
126
    } catch (Exception e) {
1✔
127
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
1✔
128
    }
1✔
129
  }
1✔
130

131
  private void buildStatementFromContext(List<XNode> list) {
132
    if (configuration.getDatabaseId() != null) {
1✔
133
      buildStatementFromContext(list, configuration.getDatabaseId());
1✔
134
    }
135
    buildStatementFromContext(list, null);
1✔
136
  }
1✔
137

138
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
139
    for (XNode context : list) {
1✔
140
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context,
1✔
141
          requiredDatabaseId);
142
      try {
143
        statementParser.parseStatementNode();
1✔
144
      } catch (IncompleteElementException e) {
1✔
145
        configuration.addIncompleteStatement(statementParser);
1✔
146
      }
1✔
147
    }
1✔
148
  }
1✔
149

150
  private void parsePendingResultMaps() {
151
    Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
1✔
152
    synchronized (incompleteResultMaps) {
1✔
153
      Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
1✔
154
      while (iter.hasNext()) {
1✔
155
        try {
156
          iter.next().resolve();
1✔
157
          iter.remove();
1✔
158
        } catch (IncompleteElementException e) {
1✔
159
          // ResultMap is still missing a resource...
160
        }
1✔
161
      }
162
    }
1✔
163
  }
1✔
164

165
  private void parsePendingCacheRefs() {
166
    Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
1✔
167
    synchronized (incompleteCacheRefs) {
1✔
168
      Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
1✔
169
      while (iter.hasNext()) {
1✔
170
        try {
171
          iter.next().resolveCacheRef();
1✔
172
          iter.remove();
1✔
173
        } catch (IncompleteElementException e) {
1✔
174
          // Cache ref is still missing a resource...
175
        }
1✔
176
      }
177
    }
1✔
178
  }
1✔
179

180
  private void parsePendingStatements() {
181
    Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
1✔
182
    synchronized (incompleteStatements) {
1✔
183
      Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
1✔
184
      while (iter.hasNext()) {
1✔
185
        try {
186
          iter.next().parseStatementNode();
1✔
187
          iter.remove();
1✔
188
        } catch (IncompleteElementException e) {
1✔
189
          // Statement is still missing a resource...
190
        }
1✔
191
      }
192
    }
1✔
193
  }
1✔
194

195
  private void cacheRefElement(XNode context) {
196
    if (context != null) {
1✔
197
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
1✔
198
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant,
1✔
199
          context.getStringAttribute("namespace"));
1✔
200
      try {
201
        cacheRefResolver.resolveCacheRef();
×
202
      } catch (IncompleteElementException e) {
1✔
203
        configuration.addIncompleteCacheRef(cacheRefResolver);
1✔
204
      }
×
205
    }
206
  }
1✔
207

208
  private void cacheElement(XNode context) {
209
    if (context != null) {
1✔
210
      String type = context.getStringAttribute("type", "PERPETUAL");
1✔
211
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
1✔
212
      String eviction = context.getStringAttribute("eviction", "LRU");
1✔
213
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
1✔
214
      Long flushInterval = context.getLongAttribute("flushInterval");
1✔
215
      Integer size = context.getIntAttribute("size");
1✔
216
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
1✔
217
      boolean blocking = context.getBooleanAttribute("blocking", false);
1✔
218
      Properties props = context.getChildrenAsProperties();
1✔
219
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
1✔
220
    }
221
  }
1✔
222

223
  private void parameterMapElement(List<XNode> list) {
224
    for (XNode parameterMapNode : list) {
1✔
225
      String id = parameterMapNode.getStringAttribute("id");
1✔
226
      String type = parameterMapNode.getStringAttribute("type");
1✔
227
      Class<?> parameterClass = resolveClass(type);
1✔
228
      List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
1✔
229
      List<ParameterMapping> parameterMappings = new ArrayList<>();
1✔
230
      for (XNode parameterNode : parameterNodes) {
1✔
231
        String property = parameterNode.getStringAttribute("property");
1✔
232
        String javaType = parameterNode.getStringAttribute("javaType");
1✔
233
        String jdbcType = parameterNode.getStringAttribute("jdbcType");
1✔
234
        String resultMap = parameterNode.getStringAttribute("resultMap");
1✔
235
        String mode = parameterNode.getStringAttribute("mode");
1✔
236
        String typeHandler = parameterNode.getStringAttribute("typeHandler");
1✔
237
        Integer numericScale = parameterNode.getIntAttribute("numericScale");
1✔
238
        ParameterMode modeEnum = resolveParameterMode(mode);
1✔
239
        Class<?> javaTypeClass = resolveClass(javaType);
1✔
240
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
1✔
241
        Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
1✔
242
        ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property,
1✔
243
            javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
244
        parameterMappings.add(parameterMapping);
1✔
245
      }
1✔
246
      builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
1✔
247
    }
1✔
248
  }
1✔
249

250
  private void resultMapElements(List<XNode> list) {
251
    for (XNode resultMapNode : list) {
1✔
252
      try {
253
        resultMapElement(resultMapNode);
1✔
254
      } catch (IncompleteElementException e) {
1✔
255
        // ignore, it will be retried
256
      }
1✔
257
    }
1✔
258
  }
1✔
259

260
  private ResultMap resultMapElement(XNode resultMapNode) {
261
    return resultMapElement(resultMapNode, Collections.emptyList(), null);
1✔
262
  }
263

264
  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings,
265
      Class<?> enclosingType) {
266
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
1✔
267
    String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType",
1✔
268
        resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
1✔
269
    Class<?> typeClass = resolveClass(type);
1✔
270
    if (typeClass == null) {
1✔
271
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
1✔
272
    }
273
    Discriminator discriminator = null;
1✔
274
    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
1✔
275
    List<XNode> resultChildren = resultMapNode.getChildren();
1✔
276
    for (XNode resultChild : resultChildren) {
1✔
277
      if ("constructor".equals(resultChild.getName())) {
1✔
278
        processConstructorElement(resultChild, typeClass, resultMappings);
1✔
279
      } else if ("discriminator".equals(resultChild.getName())) {
1✔
280
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
1✔
281
      } else {
282
        List<ResultFlag> flags = new ArrayList<>();
1✔
283
        if ("id".equals(resultChild.getName())) {
1✔
284
          flags.add(ResultFlag.ID);
1✔
285
        }
286
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
1✔
287
      }
288
    }
1✔
289
    String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
1✔
290
    String extend = resultMapNode.getStringAttribute("extends");
1✔
291
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
1✔
292
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator,
1✔
293
        resultMappings, autoMapping);
294
    try {
295
      return resultMapResolver.resolve();
1✔
296
    } catch (IncompleteElementException e) {
1✔
297
      configuration.addIncompleteResultMap(resultMapResolver);
1✔
298
      throw e;
1✔
299
    }
300
  }
301

302
  protected Class<?> inheritEnclosingType(XNode resultMapNode, Class<?> enclosingType) {
303
    if ("association".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
1!
304
      String property = resultMapNode.getStringAttribute("property");
1✔
305
      if (property != null && enclosingType != null) {
1!
306
        MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
1✔
307
        return metaResultType.getSetterType(property);
1✔
308
      }
309
    } else if ("case".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
1!
310
      return enclosingType;
1✔
311
    }
312
    return null;
1✔
313
  }
314

315
  private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) {
316
    List<XNode> argChildren = resultChild.getChildren();
1✔
317
    for (XNode argChild : argChildren) {
1✔
318
      List<ResultFlag> flags = new ArrayList<>();
1✔
319
      flags.add(ResultFlag.CONSTRUCTOR);
1✔
320
      if ("idArg".equals(argChild.getName())) {
1✔
321
        flags.add(ResultFlag.ID);
1✔
322
      }
323
      resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
1✔
324
    }
1✔
325
  }
1✔
326

327
  private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType,
328
      List<ResultMapping> resultMappings) {
329
    String column = context.getStringAttribute("column");
1✔
330
    String javaType = context.getStringAttribute("javaType");
1✔
331
    String jdbcType = context.getStringAttribute("jdbcType");
1✔
332
    String typeHandler = context.getStringAttribute("typeHandler");
1✔
333
    Class<?> javaTypeClass = resolveClass(javaType);
1✔
334
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
1✔
335
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
1✔
336
    Map<String, String> discriminatorMap = new HashMap<>();
1✔
337
    for (XNode caseChild : context.getChildren()) {
1✔
338
      String value = caseChild.getStringAttribute("value");
1✔
339
      String resultMap = caseChild.getStringAttribute("resultMap",
1✔
340
          processNestedResultMappings(caseChild, resultMappings, resultType));
1✔
341
      discriminatorMap.put(value, resultMap);
1✔
342
    }
1✔
343
    return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass,
1✔
344
        discriminatorMap);
345
  }
346

347
  private void sqlElement(List<XNode> list) {
348
    if (configuration.getDatabaseId() != null) {
1✔
349
      sqlElement(list, configuration.getDatabaseId());
1✔
350
    }
351
    sqlElement(list, null);
1✔
352
  }
1✔
353

354
  private void sqlElement(List<XNode> list, String requiredDatabaseId) {
355
    for (XNode context : list) {
1✔
356
      String databaseId = context.getStringAttribute("databaseId");
1✔
357
      String id = context.getStringAttribute("id");
1✔
358
      id = builderAssistant.applyCurrentNamespace(id, false);
1✔
359
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
1✔
360
        sqlFragments.put(id, context);
1✔
361
      }
362
    }
1✔
363
  }
1✔
364

365
  private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
366
    if (requiredDatabaseId != null) {
1✔
367
      return requiredDatabaseId.equals(databaseId);
1✔
368
    }
369
    if (databaseId != null) {
1✔
370
      return false;
1✔
371
    }
372
    if (!this.sqlFragments.containsKey(id)) {
1✔
373
      return true;
1✔
374
    }
375
    // skip this fragment if there is a previous one with a not null databaseId
376
    XNode context = this.sqlFragments.get(id);
1✔
377
    return context.getStringAttribute("databaseId") == null;
1!
378
  }
379

380
  private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
381
    String property;
382
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
1✔
383
      property = context.getStringAttribute("name");
1✔
384
    } else {
385
      property = context.getStringAttribute("property");
1✔
386
    }
387
    String column = context.getStringAttribute("column");
1✔
388
    String javaType = context.getStringAttribute("javaType");
1✔
389
    String jdbcType = context.getStringAttribute("jdbcType");
1✔
390
    String nestedSelect = context.getStringAttribute("select");
1✔
391
    String nestedResultMap = context.getStringAttribute("resultMap",
1✔
392
        () -> processNestedResultMappings(context, Collections.emptyList(), resultType));
1✔
393
    String notNullColumn = context.getStringAttribute("notNullColumn");
1✔
394
    String columnPrefix = context.getStringAttribute("columnPrefix");
1✔
395
    String typeHandler = context.getStringAttribute("typeHandler");
1✔
396
    String resultSet = context.getStringAttribute("resultSet");
1✔
397
    String foreignColumn = context.getStringAttribute("foreignColumn");
1✔
398
    boolean lazy = "lazy"
1✔
399
        .equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
1✔
400
    Class<?> javaTypeClass = resolveClass(javaType);
1✔
401
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
1✔
402
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
1✔
403
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect,
1✔
404
        nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
405
  }
406

407
  private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings,
408
      Class<?> enclosingType) {
409
    if (Arrays.asList("association", "collection", "case").contains(context.getName())
1✔
410
        && context.getStringAttribute("select") == null) {
1✔
411
      validateCollection(context, enclosingType);
1✔
412
      ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
1✔
413
      return resultMap.getId();
1✔
414
    }
415
    return null;
1✔
416
  }
417

418
  protected void validateCollection(XNode context, Class<?> enclosingType) {
419
    if ("collection".equals(context.getName()) && context.getStringAttribute("resultMap") == null
1!
420
        && context.getStringAttribute("javaType") == null) {
1✔
421
      MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
1✔
422
      String property = context.getStringAttribute("property");
1✔
423
      if (!metaResultType.hasSetter(property)) {
1✔
424
        throw new BuilderException(
1✔
425
            "Ambiguous collection type for property '" + property + "'. You must specify 'javaType' or 'resultMap'.");
426
      }
427
    }
428
  }
1✔
429

430
  private void bindMapperForNamespace() {
431
    String namespace = builderAssistant.getCurrentNamespace();
1✔
432
    if (namespace != null) {
1!
433
      Class<?> boundType = null;
1✔
434
      try {
435
        boundType = Resources.classForName(namespace);
1✔
436
      } catch (ClassNotFoundException e) {
1✔
437
        // ignore, bound type is not required
438
      }
1✔
439
      if (boundType != null && !configuration.hasMapper(boundType)) {
1✔
440
        // Spring may not know the real resource name so we set a flag
441
        // to prevent loading again this resource from the mapper interface
442
        // look at MapperAnnotationBuilder#loadXmlResource
443
        configuration.addLoadedResource("namespace:" + namespace);
1✔
444
        configuration.addMapper(boundType);
1✔
445
      }
446
    }
447
  }
1✔
448

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