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

mybatis / mybatis-3 / #2923

pending completion
#2923

push

github

web-flow
Merge pull request #2767 from FlyInWind1/no-result-type

Resolve resultType by namespace and id when not provide resultType and resultMap

13 of 13 new or added lines in 2 files covered. (100.0%)

9391 of 10754 relevant lines covered (87.33%)

0.87 hits per line

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

97.95
/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.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.annotation;
17

18
import java.io.IOException;
19
import java.io.InputStream;
20
import java.lang.annotation.Annotation;
21
import java.lang.reflect.Array;
22
import java.lang.reflect.GenericArrayType;
23
import java.lang.reflect.Method;
24
import java.lang.reflect.ParameterizedType;
25
import java.lang.reflect.Type;
26
import java.util.ArrayList;
27
import java.util.Arrays;
28
import java.util.Collection;
29
import java.util.HashMap;
30
import java.util.Iterator;
31
import java.util.List;
32
import java.util.Map;
33
import java.util.Optional;
34
import java.util.Properties;
35
import java.util.Set;
36
import java.util.stream.Collectors;
37
import java.util.stream.Stream;
38

39
import org.apache.ibatis.annotations.Arg;
40
import org.apache.ibatis.annotations.CacheNamespace;
41
import org.apache.ibatis.annotations.CacheNamespaceRef;
42
import org.apache.ibatis.annotations.Case;
43
import org.apache.ibatis.annotations.Delete;
44
import org.apache.ibatis.annotations.DeleteProvider;
45
import org.apache.ibatis.annotations.Insert;
46
import org.apache.ibatis.annotations.InsertProvider;
47
import org.apache.ibatis.annotations.Lang;
48
import org.apache.ibatis.annotations.MapKey;
49
import org.apache.ibatis.annotations.Options;
50
import org.apache.ibatis.annotations.Options.FlushCachePolicy;
51
import org.apache.ibatis.annotations.Property;
52
import org.apache.ibatis.annotations.Result;
53
import org.apache.ibatis.annotations.ResultMap;
54
import org.apache.ibatis.annotations.ResultType;
55
import org.apache.ibatis.annotations.Results;
56
import org.apache.ibatis.annotations.Select;
57
import org.apache.ibatis.annotations.SelectKey;
58
import org.apache.ibatis.annotations.SelectProvider;
59
import org.apache.ibatis.annotations.TypeDiscriminator;
60
import org.apache.ibatis.annotations.Update;
61
import org.apache.ibatis.annotations.UpdateProvider;
62
import org.apache.ibatis.binding.MapperMethod.ParamMap;
63
import org.apache.ibatis.builder.BuilderException;
64
import org.apache.ibatis.builder.CacheRefResolver;
65
import org.apache.ibatis.builder.IncompleteElementException;
66
import org.apache.ibatis.builder.MapperBuilderAssistant;
67
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
68
import org.apache.ibatis.cursor.Cursor;
69
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
70
import org.apache.ibatis.executor.keygen.KeyGenerator;
71
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
72
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
73
import org.apache.ibatis.io.Resources;
74
import org.apache.ibatis.mapping.Discriminator;
75
import org.apache.ibatis.mapping.FetchType;
76
import org.apache.ibatis.mapping.MappedStatement;
77
import org.apache.ibatis.mapping.ResultFlag;
78
import org.apache.ibatis.mapping.ResultMapping;
79
import org.apache.ibatis.mapping.ResultSetType;
80
import org.apache.ibatis.mapping.SqlCommandType;
81
import org.apache.ibatis.mapping.SqlSource;
82
import org.apache.ibatis.mapping.StatementType;
83
import org.apache.ibatis.parsing.PropertyParser;
84
import org.apache.ibatis.reflection.TypeParameterResolver;
85
import org.apache.ibatis.scripting.LanguageDriver;
86
import org.apache.ibatis.session.Configuration;
87
import org.apache.ibatis.session.ResultHandler;
88
import org.apache.ibatis.session.RowBounds;
89
import org.apache.ibatis.type.JdbcType;
90
import org.apache.ibatis.type.TypeHandler;
91
import org.apache.ibatis.type.UnknownTypeHandler;
92

93
/**
94
 * @author Clinton Begin
95
 * @author Kazuki Shimizu
96
 */
97
public class MapperAnnotationBuilder {
98

99
  private static final Set<Class<? extends Annotation>> statementAnnotationTypes = Stream
1✔
100
      .of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, UpdateProvider.class,
1✔
101
          InsertProvider.class, DeleteProvider.class)
102
      .collect(Collectors.toSet());
1✔
103

104
  private final Configuration configuration;
105
  private final MapperBuilderAssistant assistant;
106
  private final Class<?> type;
107

108
  public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
1✔
109
    String resource = type.getName().replace('.', '/') + ".java (best guess)";
1✔
110
    this.assistant = new MapperBuilderAssistant(configuration, resource);
1✔
111
    this.configuration = configuration;
1✔
112
    this.type = type;
1✔
113
  }
1✔
114

115
  public void parse() {
116
    String resource = type.toString();
1✔
117
    if (!configuration.isResourceLoaded(resource)) {
1✔
118
      loadXmlResource();
1✔
119
      configuration.addLoadedResource(resource);
1✔
120
      assistant.setCurrentNamespace(type.getName());
1✔
121
      parseCache();
1✔
122
      parseCacheRef();
1✔
123
      for (Method method : type.getMethods()) {
1✔
124
        if (!canHaveStatement(method)) {
1✔
125
          continue;
1✔
126
        }
127
        if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
1✔
128
            && method.getAnnotation(ResultMap.class) == null) {
1✔
129
          parseResultMap(method);
1✔
130
        }
131
        try {
132
          parseStatement(method);
1✔
133
        } catch (IncompleteElementException e) {
1✔
134
          configuration.addIncompleteMethod(new MethodResolver(this, method));
1✔
135
        }
1✔
136
      }
137
    }
138
    parsePendingMethods();
1✔
139
  }
1✔
140

141
  private static boolean canHaveStatement(Method method) {
142
    // issue #237
143
    return !method.isBridge() && !method.isDefault();
1✔
144
  }
145

146
  private void parsePendingMethods() {
147
    Collection<MethodResolver> incompleteMethods = configuration.getIncompleteMethods();
1✔
148
    synchronized (incompleteMethods) {
1✔
149
      Iterator<MethodResolver> iter = incompleteMethods.iterator();
1✔
150
      while (iter.hasNext()) {
1✔
151
        try {
152
          iter.next().resolve();
1✔
153
          iter.remove();
1✔
154
        } catch (IncompleteElementException e) {
1✔
155
          // This method is still missing a resource
156
        }
1✔
157
      }
158
    }
1✔
159
  }
1✔
160

161
  private void loadXmlResource() {
162
    // Spring may not know the real resource name so we check a flag
163
    // to prevent loading again a resource twice
164
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
165
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
1✔
166
      String xmlResource = type.getName().replace('.', '/') + ".xml";
1✔
167
      // #1347
168
      InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
1✔
169
      if (inputStream == null) {
1✔
170
        // Search XML mapper that is not in the module but in the classpath.
171
        try {
172
          inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
×
173
        } catch (IOException e2) {
1✔
174
          // ignore, resource is not required
175
        }
×
176
      }
177
      if (inputStream != null) {
1✔
178
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
1✔
179
        xmlParser.parse();
1✔
180
      }
181
    }
182
  }
1✔
183

184
  private void parseCache() {
185
    CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
1✔
186
    if (cacheDomain != null) {
1✔
187
      Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
1✔
188
      Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
1✔
189
      Properties props = convertToProperties(cacheDomain.properties());
1✔
190
      assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);
1✔
191
    }
192
  }
1✔
193

194
  private Properties convertToProperties(Property[] properties) {
195
    if (properties.length == 0) {
1✔
196
      return null;
1✔
197
    }
198
    Properties props = new Properties();
1✔
199
    for (Property property : properties) {
1✔
200
      props.setProperty(property.name(),
1✔
201
          PropertyParser.parse(property.value(), configuration.getVariables()));
1✔
202
    }
203
    return props;
1✔
204
  }
205

206
  private void parseCacheRef() {
207
    CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
1✔
208
    if (cacheDomainRef != null) {
1✔
209
      Class<?> refType = cacheDomainRef.value();
1✔
210
      String refName = cacheDomainRef.name();
1✔
211
      if (refType == void.class && refName.isEmpty()) {
1✔
212
        throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
1✔
213
      }
214
      if (refType != void.class && !refName.isEmpty()) {
1✔
215
        throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
1✔
216
      }
217
      String namespace = (refType != void.class) ? refType.getName() : refName;
1✔
218
      try {
219
        assistant.useCacheRef(namespace);
1✔
220
      } catch (IncompleteElementException e) {
1✔
221
        configuration.addIncompleteCacheRef(new CacheRefResolver(assistant, namespace));
1✔
222
      }
1✔
223
    }
224
  }
1✔
225

226
  private String parseResultMap(Method method) {
227
    Class<?> returnType = getReturnType(method, type);
1✔
228
    Arg[] args = method.getAnnotationsByType(Arg.class);
1✔
229
    Result[] results = method.getAnnotationsByType(Result.class);
1✔
230
    TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
1✔
231
    String resultMapId = generateResultMapName(method);
1✔
232
    applyResultMap(resultMapId, returnType, args, results, typeDiscriminator);
1✔
233
    return resultMapId;
1✔
234
  }
235

236
  private String generateResultMapName(Method method) {
237
    Results results = method.getAnnotation(Results.class);
1✔
238
    if (results != null && !results.id().isEmpty()) {
1✔
239
      return type.getName() + "." + results.id();
1✔
240
    }
241
    StringBuilder suffix = new StringBuilder();
1✔
242
    for (Class<?> c : method.getParameterTypes()) {
1✔
243
      suffix.append("-");
1✔
244
      suffix.append(c.getSimpleName());
1✔
245
    }
246
    if (suffix.length() < 1) {
1✔
247
      suffix.append("-void");
1✔
248
    }
249
    return type.getName() + "." + method.getName() + suffix;
1✔
250
  }
251

252
  private void applyResultMap(String resultMapId, Class<?> returnType, Arg[] args, Result[] results, TypeDiscriminator discriminator) {
253
    List<ResultMapping> resultMappings = new ArrayList<>();
1✔
254
    applyConstructorArgs(args, returnType, resultMappings);
1✔
255
    applyResults(results, returnType, resultMappings);
1✔
256
    Discriminator disc = applyDiscriminator(resultMapId, returnType, discriminator);
1✔
257
    // TODO add AutoMappingBehaviour
258
    assistant.addResultMap(resultMapId, returnType, null, disc, resultMappings, null);
1✔
259
    createDiscriminatorResultMaps(resultMapId, returnType, discriminator);
1✔
260
  }
1✔
261

262
  private void createDiscriminatorResultMaps(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
263
    if (discriminator != null) {
1✔
264
      for (Case c : discriminator.cases()) {
1✔
265
        String caseResultMapId = resultMapId + "-" + c.value();
1✔
266
        List<ResultMapping> resultMappings = new ArrayList<>();
1✔
267
        // issue #136
268
        applyConstructorArgs(c.constructArgs(), resultType, resultMappings);
1✔
269
        applyResults(c.results(), resultType, resultMappings);
1✔
270
        // TODO add AutoMappingBehaviour
271
        assistant.addResultMap(caseResultMapId, c.type(), resultMapId, null, resultMappings, null);
1✔
272
      }
273
    }
274
  }
1✔
275

276
  private Discriminator applyDiscriminator(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
277
    if (discriminator != null) {
1✔
278
      String column = discriminator.column();
1✔
279
      Class<?> javaType = discriminator.javaType() == void.class ? String.class : discriminator.javaType();
1✔
280
      JdbcType jdbcType = discriminator.jdbcType() == JdbcType.UNDEFINED ? null : discriminator.jdbcType();
1✔
281
      @SuppressWarnings("unchecked")
282
      Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>)
283
              (discriminator.typeHandler() == UnknownTypeHandler.class ? null : discriminator.typeHandler());
1✔
284
      Case[] cases = discriminator.cases();
1✔
285
      Map<String, String> discriminatorMap = new HashMap<>();
1✔
286
      for (Case c : cases) {
1✔
287
        String value = c.value();
1✔
288
        String caseResultMapId = resultMapId + "-" + value;
1✔
289
        discriminatorMap.put(value, caseResultMapId);
1✔
290
      }
291
      return assistant.buildDiscriminator(resultType, column, javaType, jdbcType, typeHandler, discriminatorMap);
1✔
292
    }
293
    return null;
1✔
294
  }
295

296
  void parseStatement(Method method) {
297
    final Class<?> parameterTypeClass = getParameterType(method);
1✔
298
    final LanguageDriver languageDriver = getLanguageDriver(method);
1✔
299

300
    getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {
1✔
301
      final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);
1✔
302
      final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
1✔
303
      final Options options = getAnnotationWrapper(method, false, Options.class).map(x -> (Options)x.getAnnotation()).orElse(null);
1✔
304
      final String mappedStatementId = type.getName() + "." + method.getName();
1✔
305

306
      final KeyGenerator keyGenerator;
307
      String keyProperty = null;
1✔
308
      String keyColumn = null;
1✔
309
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
1✔
310
        // first check for SelectKey annotation - that overrides everything else
311
        SelectKey selectKey = getAnnotationWrapper(method, false, SelectKey.class).map(x -> (SelectKey)x.getAnnotation()).orElse(null);
1✔
312
        if (selectKey != null) {
1✔
313
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
1✔
314
          keyProperty = selectKey.keyProperty();
1✔
315
        } else if (options == null) {
1✔
316
          keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
1✔
317
        } else {
318
          keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
1✔
319
          keyProperty = options.keyProperty();
1✔
320
          keyColumn = options.keyColumn();
1✔
321
        }
322
      } else {
1✔
323
        keyGenerator = NoKeyGenerator.INSTANCE;
1✔
324
      }
325

326
      Integer fetchSize = null;
1✔
327
      Integer timeout = null;
1✔
328
      StatementType statementType = StatementType.PREPARED;
1✔
329
      ResultSetType resultSetType = configuration.getDefaultResultSetType();
1✔
330
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
1✔
331
      boolean flushCache = !isSelect;
1✔
332
      boolean useCache = isSelect;
1✔
333
      if (options != null) {
1✔
334
        if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
1✔
335
          flushCache = true;
1✔
336
        } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
1✔
337
          flushCache = false;
1✔
338
        }
339
        useCache = options.useCache();
1✔
340
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
1✔
341
        timeout = options.timeout() > -1 ? options.timeout() : null;
1✔
342
        statementType = options.statementType();
1✔
343
        if (options.resultSetType() != ResultSetType.DEFAULT) {
1✔
344
          resultSetType = options.resultSetType();
1✔
345
        }
346
      }
347

348
      String resultMapId = null;
1✔
349
      if (isSelect) {
1✔
350
        ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
1✔
351
        if (resultMapAnnotation != null) {
1✔
352
          resultMapId = String.join(",", resultMapAnnotation.value());
1✔
353
        } else {
354
          resultMapId = generateResultMapName(method);
1✔
355
        }
356
      }
357

358
      assistant.addMappedStatement(
1✔
359
          mappedStatementId,
360
          sqlSource,
361
          statementType,
362
          sqlCommandType,
363
          fetchSize,
364
          timeout,
365
          // ParameterMapID
366
          null,
367
          parameterTypeClass,
368
          resultMapId,
369
          getReturnType(method, type),
1✔
370
          resultSetType,
371
          flushCache,
372
          useCache,
373
          // TODO gcode issue #577
374
          false,
375
          keyGenerator,
376
          keyProperty,
377
          keyColumn,
378
          statementAnnotation.getDatabaseId(),
1✔
379
          languageDriver,
380
          // ResultSets
381
          options != null ? nullOrEmpty(options.resultSets()) : null,
1✔
382
          statementAnnotation.isDirtySelect());
1✔
383
    });
1✔
384
  }
1✔
385

386
  private LanguageDriver getLanguageDriver(Method method) {
387
    Lang lang = method.getAnnotation(Lang.class);
1✔
388
    Class<? extends LanguageDriver> langClass = null;
1✔
389
    if (lang != null) {
1✔
390
      langClass = lang.value();
1✔
391
    }
392
    return configuration.getLanguageDriver(langClass);
1✔
393
  }
394

395
  private Class<?> getParameterType(Method method) {
396
    Class<?> parameterType = null;
1✔
397
    Class<?>[] parameterTypes = method.getParameterTypes();
1✔
398
    for (Class<?> currentParameterType : parameterTypes) {
1✔
399
      if (!RowBounds.class.isAssignableFrom(currentParameterType) && !ResultHandler.class.isAssignableFrom(currentParameterType)) {
1✔
400
        if (parameterType == null) {
1✔
401
          parameterType = currentParameterType;
1✔
402
        } else {
403
          // issue #135
404
          parameterType = ParamMap.class;
1✔
405
        }
406
      }
407
    }
408
    return parameterType;
1✔
409
  }
410

411
  private static Class<?> getReturnType(Method method, Class<?> type) {
412
    Class<?> returnType = method.getReturnType();
1✔
413
    Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, type);
1✔
414
    if (resolvedReturnType instanceof Class) {
1✔
415
      returnType = (Class<?>) resolvedReturnType;
1✔
416
      if (returnType.isArray()) {
1✔
417
        returnType = returnType.getComponentType();
1✔
418
      }
419
      // gcode issue #508
420
      if (void.class.equals(returnType)) {
1✔
421
        ResultType rt = method.getAnnotation(ResultType.class);
1✔
422
        if (rt != null) {
1✔
423
          returnType = rt.value();
1✔
424
        }
425
      }
1✔
426
    } else if (resolvedReturnType instanceof ParameterizedType) {
1✔
427
      ParameterizedType parameterizedType = (ParameterizedType) resolvedReturnType;
1✔
428
      Class<?> rawType = (Class<?>) parameterizedType.getRawType();
1✔
429
      if (Collection.class.isAssignableFrom(rawType) || Cursor.class.isAssignableFrom(rawType)) {
1✔
430
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
1✔
431
        if (actualTypeArguments != null && actualTypeArguments.length == 1) {
1✔
432
          Type returnTypeParameter = actualTypeArguments[0];
1✔
433
          if (returnTypeParameter instanceof Class<?>) {
1✔
434
            returnType = (Class<?>) returnTypeParameter;
1✔
435
          } else if (returnTypeParameter instanceof ParameterizedType) {
1✔
436
            // (gcode issue #443) actual type can be a also a parameterized type
437
            returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
1✔
438
          } else if (returnTypeParameter instanceof GenericArrayType) {
×
439
            Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
×
440
            // (gcode issue #525) support List<byte[]>
441
            returnType = Array.newInstance(componentType, 0).getClass();
×
442
          }
443
        }
444
      } else if (method.isAnnotationPresent(MapKey.class) && Map.class.isAssignableFrom(rawType)) {
1✔
445
        // (gcode issue 504) Do not look into Maps if there is not MapKey annotation
446
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
1✔
447
        if (actualTypeArguments != null && actualTypeArguments.length == 2) {
1✔
448
          Type returnTypeParameter = actualTypeArguments[1];
1✔
449
          if (returnTypeParameter instanceof Class<?>) {
1✔
450
            returnType = (Class<?>) returnTypeParameter;
1✔
451
          } else if (returnTypeParameter instanceof ParameterizedType) {
1✔
452
            // (gcode issue 443) actual type can be a also a parameterized type
453
            returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
1✔
454
          }
455
        }
456
      } else if (Optional.class.equals(rawType)) {
1✔
457
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
1✔
458
        Type returnTypeParameter = actualTypeArguments[0];
1✔
459
        if (returnTypeParameter instanceof Class<?>) {
1✔
460
          returnType = (Class<?>) returnTypeParameter;
1✔
461
        }
462
      }
463
    }
464

465
    return returnType;
1✔
466
  }
467

468
  private void applyResults(Result[] results, Class<?> resultType, List<ResultMapping> resultMappings) {
469
    for (Result result : results) {
1✔
470
      List<ResultFlag> flags = new ArrayList<>();
1✔
471
      if (result.id()) {
1✔
472
        flags.add(ResultFlag.ID);
1✔
473
      }
474
      @SuppressWarnings("unchecked")
475
      Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>)
476
              ((result.typeHandler() == UnknownTypeHandler.class) ? null : result.typeHandler());
1✔
477
      boolean hasNestedResultMap = hasNestedResultMap(result);
1✔
478
      ResultMapping resultMapping = assistant.buildResultMapping(
1✔
479
          resultType,
480
          nullOrEmpty(result.property()),
1✔
481
          nullOrEmpty(result.column()),
1✔
482
          result.javaType() == void.class ? null : result.javaType(),
1✔
483
          result.jdbcType() == JdbcType.UNDEFINED ? null : result.jdbcType(),
1✔
484
          hasNestedSelect(result) ? nestedSelectId(result) : null,
1✔
485
          hasNestedResultMap ? nestedResultMapId(result) : null,
1✔
486
          null,
487
          hasNestedResultMap ? findColumnPrefix(result) : null,
1✔
488
          typeHandler,
489
          flags,
490
          null,
491
          null,
492
          isLazy(result));
1✔
493
      resultMappings.add(resultMapping);
1✔
494
    }
495
  }
1✔
496

497
  private String findColumnPrefix(Result result) {
498
    String columnPrefix = result.one().columnPrefix();
1✔
499
    if (columnPrefix.length() < 1) {
1✔
500
      columnPrefix = result.many().columnPrefix();
1✔
501
    }
502
    return columnPrefix;
1✔
503
  }
504

505
  private String nestedResultMapId(Result result) {
506
    String resultMapId = result.one().resultMap();
1✔
507
    if (resultMapId.length() < 1) {
1✔
508
      resultMapId = result.many().resultMap();
1✔
509
    }
510
    if (!resultMapId.contains(".")) {
1✔
511
      resultMapId = type.getName() + "." + resultMapId;
1✔
512
    }
513
    return resultMapId;
1✔
514
  }
515

516
  private boolean hasNestedResultMap(Result result) {
517
    if (result.one().resultMap().length() > 0 && result.many().resultMap().length() > 0) {
1✔
518
      throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
×
519
    }
520
    return result.one().resultMap().length() > 0 || result.many().resultMap().length() > 0;
1✔
521
  }
522

523
  private String nestedSelectId(Result result) {
524
    String nestedSelect = result.one().select();
1✔
525
    if (nestedSelect.length() < 1) {
1✔
526
      nestedSelect = result.many().select();
1✔
527
    }
528
    if (!nestedSelect.contains(".")) {
1✔
529
      nestedSelect = type.getName() + "." + nestedSelect;
1✔
530
    }
531
    return nestedSelect;
1✔
532
  }
533

534
  private boolean isLazy(Result result) {
535
    boolean isLazy = configuration.isLazyLoadingEnabled();
1✔
536
    if (result.one().select().length() > 0 && FetchType.DEFAULT != result.one().fetchType()) {
1✔
537
      isLazy = result.one().fetchType() == FetchType.LAZY;
1✔
538
    } else if (result.many().select().length() > 0 && FetchType.DEFAULT != result.many().fetchType()) {
1✔
539
      isLazy = result.many().fetchType() == FetchType.LAZY;
1✔
540
    }
541
    return isLazy;
1✔
542
  }
543

544
  private boolean hasNestedSelect(Result result) {
545
    if (result.one().select().length() > 0 && result.many().select().length() > 0) {
1✔
546
      throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
1✔
547
    }
548
    return result.one().select().length() > 0 || result.many().select().length() > 0;
1✔
549
  }
550

551
  private void applyConstructorArgs(Arg[] args, Class<?> resultType, List<ResultMapping> resultMappings) {
552
    for (Arg arg : args) {
1✔
553
      List<ResultFlag> flags = new ArrayList<>();
1✔
554
      flags.add(ResultFlag.CONSTRUCTOR);
1✔
555
      if (arg.id()) {
1✔
556
        flags.add(ResultFlag.ID);
1✔
557
      }
558
      @SuppressWarnings("unchecked")
559
      Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>)
560
              (arg.typeHandler() == UnknownTypeHandler.class ? null : arg.typeHandler());
1✔
561
      ResultMapping resultMapping = assistant.buildResultMapping(
1✔
562
          resultType,
563
          nullOrEmpty(arg.name()),
1✔
564
          nullOrEmpty(arg.column()),
1✔
565
          arg.javaType() == void.class ? null : arg.javaType(),
1✔
566
          arg.jdbcType() == JdbcType.UNDEFINED ? null : arg.jdbcType(),
1✔
567
          nullOrEmpty(arg.select()),
1✔
568
          nullOrEmpty(arg.resultMap()),
1✔
569
          null,
570
          nullOrEmpty(arg.columnPrefix()),
1✔
571
          typeHandler,
572
          flags,
573
          null,
574
          null,
575
          false);
576
      resultMappings.add(resultMapping);
1✔
577
    }
578
  }
1✔
579

580
  private String nullOrEmpty(String value) {
581
    return value == null || value.trim().length() == 0 ? null : value;
1✔
582
  }
583

584
  private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, String baseStatementId, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
585
    String id = baseStatementId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
1✔
586
    Class<?> resultTypeClass = selectKeyAnnotation.resultType();
1✔
587
    StatementType statementType = selectKeyAnnotation.statementType();
1✔
588
    String keyProperty = selectKeyAnnotation.keyProperty();
1✔
589
    String keyColumn = selectKeyAnnotation.keyColumn();
1✔
590
    boolean executeBefore = selectKeyAnnotation.before();
1✔
591

592
    // defaults
593
    boolean useCache = false;
1✔
594
    KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
1✔
595
    Integer fetchSize = null;
1✔
596
    Integer timeout = null;
1✔
597
    boolean flushCache = false;
1✔
598
    String parameterMap = null;
1✔
599
    String resultMap = null;
1✔
600
    ResultSetType resultSetTypeEnum = null;
1✔
601
    String databaseId = selectKeyAnnotation.databaseId().isEmpty() ? null : selectKeyAnnotation.databaseId();
1✔
602

603
    SqlSource sqlSource = buildSqlSource(selectKeyAnnotation, parameterTypeClass, languageDriver, null);
1✔
604
    SqlCommandType sqlCommandType = SqlCommandType.SELECT;
1✔
605

606
    assistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum,
1✔
607
        flushCache, useCache, false,
608
        keyGenerator, keyProperty, keyColumn, databaseId, languageDriver, null, false);
609

610
    id = assistant.applyCurrentNamespace(id, false);
1✔
611

612
    MappedStatement keyStatement = configuration.getMappedStatement(id, false);
1✔
613
    SelectKeyGenerator answer = new SelectKeyGenerator(keyStatement, executeBefore);
1✔
614
    configuration.addKeyGenerator(id, answer);
1✔
615
    return answer;
1✔
616
  }
617

618
  private SqlSource buildSqlSource(Annotation annotation, Class<?> parameterType, LanguageDriver languageDriver,
619
      Method method) {
620
    if (annotation instanceof Select) {
1✔
621
      return buildSqlSourceFromStrings(((Select) annotation).value(), parameterType, languageDriver);
1✔
622
    } else if (annotation instanceof Update) {
1✔
623
      return buildSqlSourceFromStrings(((Update) annotation).value(), parameterType, languageDriver);
1✔
624
    } else if (annotation instanceof Insert) {
1✔
625
      return buildSqlSourceFromStrings(((Insert) annotation).value(), parameterType, languageDriver);
1✔
626
    } else if (annotation instanceof Delete) {
1✔
627
      return buildSqlSourceFromStrings(((Delete) annotation).value(), parameterType, languageDriver);
1✔
628
    } else if (annotation instanceof SelectKey) {
1✔
629
      return buildSqlSourceFromStrings(((SelectKey) annotation).statement(), parameterType, languageDriver);
1✔
630
    }
631
    return new ProviderSqlSource(assistant.getConfiguration(), annotation, type, method);
1✔
632
  }
633

634
  private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass,
635
      LanguageDriver languageDriver) {
636
    return languageDriver.createSqlSource(configuration, String.join(" ", strings).trim(), parameterTypeClass);
1✔
637
  }
638

639
  @SafeVarargs
640
  private final Optional<AnnotationWrapper> getAnnotationWrapper(Method method, boolean errorIfNoMatch,
641
      Class<? extends Annotation>... targetTypes) {
642
    return getAnnotationWrapper(method, errorIfNoMatch, Arrays.asList(targetTypes));
1✔
643
  }
644

645
  private Optional<AnnotationWrapper> getAnnotationWrapper(Method method, boolean errorIfNoMatch,
646
      Collection<Class<? extends Annotation>> targetTypes) {
647
    String databaseId = configuration.getDatabaseId();
1✔
648
    Map<String, AnnotationWrapper> statementAnnotations = targetTypes.stream()
1✔
649
        .flatMap(x -> Arrays.stream(method.getAnnotationsByType(x))).map(AnnotationWrapper::new)
1✔
650
        .collect(Collectors.toMap(AnnotationWrapper::getDatabaseId, x -> x, (existing, duplicate) -> {
1✔
651
          throw new BuilderException(String.format("Detected conflicting annotations '%s' and '%s' on '%s'.",
1✔
652
              existing.getAnnotation(), duplicate.getAnnotation(),
1✔
653
              method.getDeclaringClass().getName() + "." + method.getName()));
1✔
654
        }));
655
    AnnotationWrapper annotationWrapper = null;
1✔
656
    if (databaseId != null) {
1✔
657
      annotationWrapper = statementAnnotations.get(databaseId);
1✔
658
    }
659
    if (annotationWrapper == null) {
1✔
660
      annotationWrapper = statementAnnotations.get("");
1✔
661
    }
662
    if (errorIfNoMatch && annotationWrapper == null && !statementAnnotations.isEmpty()) {
1✔
663
      // Annotations exist, but there is no matching one for the specified databaseId
664
      throw new BuilderException(
1✔
665
          String.format(
1✔
666
              "Could not find a statement annotation that correspond a current database or default statement on method '%s.%s'. Current database id is [%s].",
667
              method.getDeclaringClass().getName(), method.getName(), databaseId));
1✔
668
    }
669
    return Optional.ofNullable(annotationWrapper);
1✔
670
  }
671

672
  public static Class<?> getMethodReturnType(String mapperFqn, String localStatementId) {
673
    if (mapperFqn == null || localStatementId == null) {
1✔
674
      return null;
×
675
    }
676
    try {
677
      Class<?> mapperClass = Resources.classForName(mapperFqn);
1✔
678
      for (Method method : mapperClass.getMethods()) {
1✔
679
        if (method.getName().equals(localStatementId) && canHaveStatement(method)) {
1✔
680
          return getReturnType(method, mapperClass);
1✔
681
        }
682
      }
683
    } catch (ClassNotFoundException e) {
1✔
684
      // No corresponding mapper interface which is OK
685
    }
1✔
686
    return null;
1✔
687
  }
688

689
  private class AnnotationWrapper {
690
    private final Annotation annotation;
691
    private final String databaseId;
692
    private final SqlCommandType sqlCommandType;
693
    private boolean dirtySelect;
694

695
    AnnotationWrapper(Annotation annotation) {
1✔
696
      super();
1✔
697
      this.annotation = annotation;
1✔
698
      if (annotation instanceof Select) {
1✔
699
        databaseId = ((Select) annotation).databaseId();
1✔
700
        sqlCommandType = SqlCommandType.SELECT;
1✔
701
        dirtySelect = ((Select) annotation).affectData();
1✔
702
      } else if (annotation instanceof Update) {
1✔
703
        databaseId = ((Update) annotation).databaseId();
1✔
704
        sqlCommandType = SqlCommandType.UPDATE;
1✔
705
      } else if (annotation instanceof Insert) {
1✔
706
        databaseId = ((Insert) annotation).databaseId();
1✔
707
        sqlCommandType = SqlCommandType.INSERT;
1✔
708
      } else if (annotation instanceof Delete) {
1✔
709
        databaseId = ((Delete) annotation).databaseId();
1✔
710
        sqlCommandType = SqlCommandType.DELETE;
1✔
711
      } else if (annotation instanceof SelectProvider) {
1✔
712
        databaseId = ((SelectProvider) annotation).databaseId();
1✔
713
        sqlCommandType = SqlCommandType.SELECT;
1✔
714
        dirtySelect = ((SelectProvider) annotation).affectData();
1✔
715
      } else if (annotation instanceof UpdateProvider) {
1✔
716
        databaseId = ((UpdateProvider) annotation).databaseId();
1✔
717
        sqlCommandType = SqlCommandType.UPDATE;
1✔
718
      } else if (annotation instanceof InsertProvider) {
1✔
719
        databaseId = ((InsertProvider) annotation).databaseId();
1✔
720
        sqlCommandType = SqlCommandType.INSERT;
1✔
721
      } else if (annotation instanceof DeleteProvider) {
1✔
722
        databaseId = ((DeleteProvider) annotation).databaseId();
1✔
723
        sqlCommandType = SqlCommandType.DELETE;
1✔
724
      } else {
725
        sqlCommandType = SqlCommandType.UNKNOWN;
1✔
726
        if (annotation instanceof Options) {
1✔
727
          databaseId = ((Options) annotation).databaseId();
1✔
728
        } else if (annotation instanceof SelectKey) {
1✔
729
          databaseId = ((SelectKey) annotation).databaseId();
1✔
730
        } else {
731
          databaseId = "";
×
732
        }
733
      }
734
    }
1✔
735

736
    Annotation getAnnotation() {
737
      return annotation;
1✔
738
    }
739

740
    SqlCommandType getSqlCommandType() {
741
      return sqlCommandType;
1✔
742
    }
743

744
    String getDatabaseId() {
745
      return databaseId;
1✔
746
    }
747

748
    boolean isDirtySelect() {
749
      return dirtySelect;
1✔
750
    }
751
  }
752
}
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