1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
95
96
97 public class MapperAnnotationBuilder {
98
99 private static final Set<Class<? extends Annotation>> statementAnnotationTypes = Stream
100 .of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, UpdateProvider.class,
101 InsertProvider.class, DeleteProvider.class)
102 .collect(Collectors.toSet());
103
104 private final Configuration configuration;
105 private final MapperBuilderAssistant assistant;
106 private final Class<?> type;
107
108 public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
109 String resource = type.getName().replace('.', '/') + ".java (best guess)";
110 this.assistant = new MapperBuilderAssistant(configuration, resource);
111 this.configuration = configuration;
112 this.type = type;
113 }
114
115 public void parse() {
116 String resource = type.toString();
117 if (!configuration.isResourceLoaded(resource)) {
118 loadXmlResource();
119 configuration.addLoadedResource(resource);
120 assistant.setCurrentNamespace(type.getName());
121 parseCache();
122 parseCacheRef();
123 for (Method method : type.getMethods()) {
124 if (!canHaveStatement(method)) {
125 continue;
126 }
127 if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
128 && method.getAnnotation(ResultMap.class) == null) {
129 parseResultMap(method);
130 }
131 try {
132 parseStatement(method);
133 } catch (IncompleteElementException e) {
134 configuration.addIncompleteMethod(new MethodResolver(this, method));
135 }
136 }
137 }
138 parsePendingMethods();
139 }
140
141 private boolean canHaveStatement(Method method) {
142
143 return !method.isBridge() && !method.isDefault();
144 }
145
146 private void parsePendingMethods() {
147 Collection<MethodResolver> incompleteMethods = configuration.getIncompleteMethods();
148 synchronized (incompleteMethods) {
149 Iterator<MethodResolver> iter = incompleteMethods.iterator();
150 while (iter.hasNext()) {
151 try {
152 iter.next().resolve();
153 iter.remove();
154 } catch (IncompleteElementException e) {
155
156 }
157 }
158 }
159 }
160
161 private void loadXmlResource() {
162
163
164
165 if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
166 String xmlResource = type.getName().replace('.', '/') + ".xml";
167
168 InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
169 if (inputStream == null) {
170
171 try {
172 inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
173 } catch (IOException e2) {
174
175 }
176 }
177 if (inputStream != null) {
178 XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
179 xmlParser.parse();
180 }
181 }
182 }
183
184 private void parseCache() {
185 CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
186 if (cacheDomain != null) {
187 Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
188 Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
189 Properties props = convertToProperties(cacheDomain.properties());
190 assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);
191 }
192 }
193
194 private Properties convertToProperties(Property[] properties) {
195 if (properties.length == 0) {
196 return null;
197 }
198 Properties props = new Properties();
199 for (Property property : properties) {
200 props.setProperty(property.name(),
201 PropertyParser.parse(property.value(), configuration.getVariables()));
202 }
203 return props;
204 }
205
206 private void parseCacheRef() {
207 CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
208 if (cacheDomainRef != null) {
209 Class<?> refType = cacheDomainRef.value();
210 String refName = cacheDomainRef.name();
211 if (refType == void.class && refName.isEmpty()) {
212 throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
213 }
214 if (refType != void.class && !refName.isEmpty()) {
215 throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
216 }
217 String namespace = (refType != void.class) ? refType.getName() : refName;
218 try {
219 assistant.useCacheRef(namespace);
220 } catch (IncompleteElementException e) {
221 configuration.addIncompleteCacheRef(new CacheRefResolver(assistant, namespace));
222 }
223 }
224 }
225
226 private String parseResultMap(Method method) {
227 Class<?> returnType = getReturnType(method);
228 Arg[] args = method.getAnnotationsByType(Arg.class);
229 Result[] results = method.getAnnotationsByType(Result.class);
230 TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
231 String resultMapId = generateResultMapName(method);
232 applyResultMap(resultMapId, returnType, args, results, typeDiscriminator);
233 return resultMapId;
234 }
235
236 private String generateResultMapName(Method method) {
237 Results results = method.getAnnotation(Results.class);
238 if (results != null && !results.id().isEmpty()) {
239 return type.getName() + "." + results.id();
240 }
241 StringBuilder suffix = new StringBuilder();
242 for (Class<?> c : method.getParameterTypes()) {
243 suffix.append("-");
244 suffix.append(c.getSimpleName());
245 }
246 if (suffix.length() < 1) {
247 suffix.append("-void");
248 }
249 return type.getName() + "." + method.getName() + suffix;
250 }
251
252 private void applyResultMap(String resultMapId, Class<?> returnType, Arg[] args, Result[] results, TypeDiscriminator discriminator) {
253 List<ResultMapping> resultMappings = new ArrayList<>();
254 applyConstructorArgs(args, returnType, resultMappings);
255 applyResults(results, returnType, resultMappings);
256 Discriminator disc = applyDiscriminator(resultMapId, returnType, discriminator);
257
258 assistant.addResultMap(resultMapId, returnType, null, disc, resultMappings, null);
259 createDiscriminatorResultMaps(resultMapId, returnType, discriminator);
260 }
261
262 private void createDiscriminatorResultMaps(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
263 if (discriminator != null) {
264 for (Case c : discriminator.cases()) {
265 String caseResultMapId = resultMapId + "-" + c.value();
266 List<ResultMapping> resultMappings = new ArrayList<>();
267
268 applyConstructorArgs(c.constructArgs(), resultType, resultMappings);
269 applyResults(c.results(), resultType, resultMappings);
270
271 assistant.addResultMap(caseResultMapId, c.type(), resultMapId, null, resultMappings, null);
272 }
273 }
274 }
275
276 private Discriminator applyDiscriminator(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
277 if (discriminator != null) {
278 String column = discriminator.column();
279 Class<?> javaType = discriminator.javaType() == void.class ? String.class : discriminator.javaType();
280 JdbcType jdbcType = discriminator.jdbcType() == JdbcType.UNDEFINED ? null : discriminator.jdbcType();
281 @SuppressWarnings("unchecked")
282 Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>)
283 (discriminator.typeHandler() == UnknownTypeHandler.class ? null : discriminator.typeHandler());
284 Case[] cases = discriminator.cases();
285 Map<String, String> discriminatorMap = new HashMap<>();
286 for (Case c : cases) {
287 String value = c.value();
288 String caseResultMapId = resultMapId + "-" + value;
289 discriminatorMap.put(value, caseResultMapId);
290 }
291 return assistant.buildDiscriminator(resultType, column, javaType, jdbcType, typeHandler, discriminatorMap);
292 }
293 return null;
294 }
295
296 void parseStatement(Method method) {
297 final Class<?> parameterTypeClass = getParameterType(method);
298 final LanguageDriver languageDriver = getLanguageDriver(method);
299
300 getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {
301 final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);
302 final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
303 final Options options = getAnnotationWrapper(method, false, Options.class).map(x -> (Options)x.getAnnotation()).orElse(null);
304 final String mappedStatementId = type.getName() + "." + method.getName();
305
306 final KeyGenerator keyGenerator;
307 String keyProperty = null;
308 String keyColumn = null;
309 if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
310
311 SelectKey selectKey = getAnnotationWrapper(method, false, SelectKey.class).map(x -> (SelectKey)x.getAnnotation()).orElse(null);
312 if (selectKey != null) {
313 keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
314 keyProperty = selectKey.keyProperty();
315 } else if (options == null) {
316 keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
317 } else {
318 keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
319 keyProperty = options.keyProperty();
320 keyColumn = options.keyColumn();
321 }
322 } else {
323 keyGenerator = NoKeyGenerator.INSTANCE;
324 }
325
326 Integer fetchSize = null;
327 Integer timeout = null;
328 StatementType statementType = StatementType.PREPARED;
329 ResultSetType resultSetType = configuration.getDefaultResultSetType();
330 boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
331 boolean flushCache = !isSelect;
332 boolean useCache = isSelect;
333 if (options != null) {
334 if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
335 flushCache = true;
336 } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
337 flushCache = false;
338 }
339 useCache = options.useCache();
340 fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null;
341 timeout = options.timeout() > -1 ? options.timeout() : null;
342 statementType = options.statementType();
343 if (options.resultSetType() != ResultSetType.DEFAULT) {
344 resultSetType = options.resultSetType();
345 }
346 }
347
348 String resultMapId = null;
349 if (isSelect) {
350 ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
351 if (resultMapAnnotation != null) {
352 resultMapId = String.join(",", resultMapAnnotation.value());
353 } else {
354 resultMapId = generateResultMapName(method);
355 }
356 }
357
358 assistant.addMappedStatement(
359 mappedStatementId,
360 sqlSource,
361 statementType,
362 sqlCommandType,
363 fetchSize,
364 timeout,
365
366 null,
367 parameterTypeClass,
368 resultMapId,
369 getReturnType(method),
370 resultSetType,
371 flushCache,
372 useCache,
373
374 false,
375 keyGenerator,
376 keyProperty,
377 keyColumn,
378 statementAnnotation.getDatabaseId(),
379 languageDriver,
380
381 options != null ? nullOrEmpty(options.resultSets()) : null);
382 });
383 }
384
385 private LanguageDriver getLanguageDriver(Method method) {
386 Lang lang = method.getAnnotation(Lang.class);
387 Class<? extends LanguageDriver> langClass = null;
388 if (lang != null) {
389 langClass = lang.value();
390 }
391 return configuration.getLanguageDriver(langClass);
392 }
393
394 private Class<?> getParameterType(Method method) {
395 Class<?> parameterType = null;
396 Class<?>[] parameterTypes = method.getParameterTypes();
397 for (Class<?> currentParameterType : parameterTypes) {
398 if (!RowBounds.class.isAssignableFrom(currentParameterType) && !ResultHandler.class.isAssignableFrom(currentParameterType)) {
399 if (parameterType == null) {
400 parameterType = currentParameterType;
401 } else {
402
403 parameterType = ParamMap.class;
404 }
405 }
406 }
407 return parameterType;
408 }
409
410 private Class<?> getReturnType(Method method) {
411 Class<?> returnType = method.getReturnType();
412 Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, type);
413 if (resolvedReturnType instanceof Class) {
414 returnType = (Class<?>) resolvedReturnType;
415 if (returnType.isArray()) {
416 returnType = returnType.getComponentType();
417 }
418
419 if (void.class.equals(returnType)) {
420 ResultType rt = method.getAnnotation(ResultType.class);
421 if (rt != null) {
422 returnType = rt.value();
423 }
424 }
425 } else if (resolvedReturnType instanceof ParameterizedType) {
426 ParameterizedType parameterizedType = (ParameterizedType) resolvedReturnType;
427 Class<?> rawType = (Class<?>) parameterizedType.getRawType();
428 if (Collection.class.isAssignableFrom(rawType) || Cursor.class.isAssignableFrom(rawType)) {
429 Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
430 if (actualTypeArguments != null && actualTypeArguments.length == 1) {
431 Type returnTypeParameter = actualTypeArguments[0];
432 if (returnTypeParameter instanceof Class<?>) {
433 returnType = (Class<?>) returnTypeParameter;
434 } else if (returnTypeParameter instanceof ParameterizedType) {
435
436 returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
437 } else if (returnTypeParameter instanceof GenericArrayType) {
438 Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
439
440 returnType = Array.newInstance(componentType, 0).getClass();
441 }
442 }
443 } else if (method.isAnnotationPresent(MapKey.class) && Map.class.isAssignableFrom(rawType)) {
444
445 Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
446 if (actualTypeArguments != null && actualTypeArguments.length == 2) {
447 Type returnTypeParameter = actualTypeArguments[1];
448 if (returnTypeParameter instanceof Class<?>) {
449 returnType = (Class<?>) returnTypeParameter;
450 } else if (returnTypeParameter instanceof ParameterizedType) {
451
452 returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
453 }
454 }
455 } else if (Optional.class.equals(rawType)) {
456 Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
457 Type returnTypeParameter = actualTypeArguments[0];
458 if (returnTypeParameter instanceof Class<?>) {
459 returnType = (Class<?>) returnTypeParameter;
460 }
461 }
462 }
463
464 return returnType;
465 }
466
467 private void applyResults(Result[] results, Class<?> resultType, List<ResultMapping> resultMappings) {
468 for (Result result : results) {
469 List<ResultFlag> flags = new ArrayList<>();
470 if (result.id()) {
471 flags.add(ResultFlag.ID);
472 }
473 @SuppressWarnings("unchecked")
474 Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>)
475 ((result.typeHandler() == UnknownTypeHandler.class) ? null : result.typeHandler());
476 boolean hasNestedResultMap = hasNestedResultMap(result);
477 ResultMapping resultMapping = assistant.buildResultMapping(
478 resultType,
479 nullOrEmpty(result.property()),
480 nullOrEmpty(result.column()),
481 result.javaType() == void.class ? null : result.javaType(),
482 result.jdbcType() == JdbcType.UNDEFINED ? null : result.jdbcType(),
483 hasNestedSelect(result) ? nestedSelectId(result) : null,
484 hasNestedResultMap ? nestedResultMapId(result) : null,
485 null,
486 hasNestedResultMap ? findColumnPrefix(result) : null,
487 typeHandler,
488 flags,
489 null,
490 null,
491 isLazy(result));
492 resultMappings.add(resultMapping);
493 }
494 }
495
496 private String findColumnPrefix(Result result) {
497 String columnPrefix = result.one().columnPrefix();
498 if (columnPrefix.length() < 1) {
499 columnPrefix = result.many().columnPrefix();
500 }
501 return columnPrefix;
502 }
503
504 private String nestedResultMapId(Result result) {
505 String resultMapId = result.one().resultMap();
506 if (resultMapId.length() < 1) {
507 resultMapId = result.many().resultMap();
508 }
509 if (!resultMapId.contains(".")) {
510 resultMapId = type.getName() + "." + resultMapId;
511 }
512 return resultMapId;
513 }
514
515 private boolean hasNestedResultMap(Result result) {
516 if (result.one().resultMap().length() > 0 && result.many().resultMap().length() > 0) {
517 throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
518 }
519 return result.one().resultMap().length() > 0 || result.many().resultMap().length() > 0;
520 }
521
522 private String nestedSelectId(Result result) {
523 String nestedSelect = result.one().select();
524 if (nestedSelect.length() < 1) {
525 nestedSelect = result.many().select();
526 }
527 if (!nestedSelect.contains(".")) {
528 nestedSelect = type.getName() + "." + nestedSelect;
529 }
530 return nestedSelect;
531 }
532
533 private boolean isLazy(Result result) {
534 boolean isLazy = configuration.isLazyLoadingEnabled();
535 if (result.one().select().length() > 0 && FetchType.DEFAULT != result.one().fetchType()) {
536 isLazy = result.one().fetchType() == FetchType.LAZY;
537 } else if (result.many().select().length() > 0 && FetchType.DEFAULT != result.many().fetchType()) {
538 isLazy = result.many().fetchType() == FetchType.LAZY;
539 }
540 return isLazy;
541 }
542
543 private boolean hasNestedSelect(Result result) {
544 if (result.one().select().length() > 0 && result.many().select().length() > 0) {
545 throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
546 }
547 return result.one().select().length() > 0 || result.many().select().length() > 0;
548 }
549
550 private void applyConstructorArgs(Arg[] args, Class<?> resultType, List<ResultMapping> resultMappings) {
551 for (Arg arg : args) {
552 List<ResultFlag> flags = new ArrayList<>();
553 flags.add(ResultFlag.CONSTRUCTOR);
554 if (arg.id()) {
555 flags.add(ResultFlag.ID);
556 }
557 @SuppressWarnings("unchecked")
558 Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>)
559 (arg.typeHandler() == UnknownTypeHandler.class ? null : arg.typeHandler());
560 ResultMapping resultMapping = assistant.buildResultMapping(
561 resultType,
562 nullOrEmpty(arg.name()),
563 nullOrEmpty(arg.column()),
564 arg.javaType() == void.class ? null : arg.javaType(),
565 arg.jdbcType() == JdbcType.UNDEFINED ? null : arg.jdbcType(),
566 nullOrEmpty(arg.select()),
567 nullOrEmpty(arg.resultMap()),
568 null,
569 nullOrEmpty(arg.columnPrefix()),
570 typeHandler,
571 flags,
572 null,
573 null,
574 false);
575 resultMappings.add(resultMapping);
576 }
577 }
578
579 private String nullOrEmpty(String value) {
580 return value == null || value.trim().length() == 0 ? null : value;
581 }
582
583 private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, String baseStatementId, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
584 String id = baseStatementId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
585 Class<?> resultTypeClass = selectKeyAnnotation.resultType();
586 StatementType statementType = selectKeyAnnotation.statementType();
587 String keyProperty = selectKeyAnnotation.keyProperty();
588 String keyColumn = selectKeyAnnotation.keyColumn();
589 boolean executeBefore = selectKeyAnnotation.before();
590
591
592 boolean useCache = false;
593 KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
594 Integer fetchSize = null;
595 Integer timeout = null;
596 boolean flushCache = false;
597 String parameterMap = null;
598 String resultMap = null;
599 ResultSetType resultSetTypeEnum = null;
600 String databaseId = selectKeyAnnotation.databaseId().isEmpty() ? null : selectKeyAnnotation.databaseId();
601
602 SqlSource sqlSource = buildSqlSource(selectKeyAnnotation, parameterTypeClass, languageDriver, null);
603 SqlCommandType sqlCommandType = SqlCommandType.SELECT;
604
605 assistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum,
606 flushCache, useCache, false,
607 keyGenerator, keyProperty, keyColumn, databaseId, languageDriver, null);
608
609 id = assistant.applyCurrentNamespace(id, false);
610
611 MappedStatement keyStatement = configuration.getMappedStatement(id, false);
612 SelectKeyGenerator answer = new SelectKeyGenerator(keyStatement, executeBefore);
613 configuration.addKeyGenerator(id, answer);
614 return answer;
615 }
616
617 private SqlSource buildSqlSource(Annotation annotation, Class<?> parameterType, LanguageDriver languageDriver,
618 Method method) {
619 if (annotation instanceof Select) {
620 return buildSqlSourceFromStrings(((Select) annotation).value(), parameterType, languageDriver);
621 } else if (annotation instanceof Update) {
622 return buildSqlSourceFromStrings(((Update) annotation).value(), parameterType, languageDriver);
623 } else if (annotation instanceof Insert) {
624 return buildSqlSourceFromStrings(((Insert) annotation).value(), parameterType, languageDriver);
625 } else if (annotation instanceof Delete) {
626 return buildSqlSourceFromStrings(((Delete) annotation).value(), parameterType, languageDriver);
627 } else if (annotation instanceof SelectKey) {
628 return buildSqlSourceFromStrings(((SelectKey) annotation).statement(), parameterType, languageDriver);
629 }
630 return new ProviderSqlSource(assistant.getConfiguration(), annotation, type, method);
631 }
632
633 private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass,
634 LanguageDriver languageDriver) {
635 return languageDriver.createSqlSource(configuration, String.join(" ", strings).trim(), parameterTypeClass);
636 }
637
638 @SafeVarargs
639 private final Optional<AnnotationWrapper> getAnnotationWrapper(Method method, boolean errorIfNoMatch,
640 Class<? extends Annotation>... targetTypes) {
641 return getAnnotationWrapper(method, errorIfNoMatch, Arrays.asList(targetTypes));
642 }
643
644 private Optional<AnnotationWrapper> getAnnotationWrapper(Method method, boolean errorIfNoMatch,
645 Collection<Class<? extends Annotation>> targetTypes) {
646 String databaseId = configuration.getDatabaseId();
647 Map<String, AnnotationWrapper> statementAnnotations = targetTypes.stream()
648 .flatMap(x -> Arrays.stream(method.getAnnotationsByType(x))).map(AnnotationWrapper::new)
649 .collect(Collectors.toMap(AnnotationWrapper::getDatabaseId, x -> x, (existing, duplicate) -> {
650 throw new BuilderException(String.format("Detected conflicting annotations '%s' and '%s' on '%s'.",
651 existing.getAnnotation(), duplicate.getAnnotation(),
652 method.getDeclaringClass().getName() + "." + method.getName()));
653 }));
654 AnnotationWrapper annotationWrapper = null;
655 if (databaseId != null) {
656 annotationWrapper = statementAnnotations.get(databaseId);
657 }
658 if (annotationWrapper == null) {
659 annotationWrapper = statementAnnotations.get("");
660 }
661 if (errorIfNoMatch && annotationWrapper == null && !statementAnnotations.isEmpty()) {
662
663 throw new BuilderException(
664 String.format(
665 "Could not find a statement annotation that correspond a current database or default statement on method '%s.%s'. Current database id is [%s].",
666 method.getDeclaringClass().getName(), method.getName(), databaseId));
667 }
668 return Optional.ofNullable(annotationWrapper);
669 }
670
671 private class AnnotationWrapper {
672 private final Annotation annotation;
673 private final String databaseId;
674 private final SqlCommandType sqlCommandType;
675
676 AnnotationWrapper(Annotation annotation) {
677 super();
678 this.annotation = annotation;
679 if (annotation instanceof Select) {
680 databaseId = ((Select) annotation).databaseId();
681 sqlCommandType = SqlCommandType.SELECT;
682 } else if (annotation instanceof Update) {
683 databaseId = ((Update) annotation).databaseId();
684 sqlCommandType = SqlCommandType.UPDATE;
685 } else if (annotation instanceof Insert) {
686 databaseId = ((Insert) annotation).databaseId();
687 sqlCommandType = SqlCommandType.INSERT;
688 } else if (annotation instanceof Delete) {
689 databaseId = ((Delete) annotation).databaseId();
690 sqlCommandType = SqlCommandType.DELETE;
691 } else if (annotation instanceof SelectProvider) {
692 databaseId = ((SelectProvider) annotation).databaseId();
693 sqlCommandType = SqlCommandType.SELECT;
694 } else if (annotation instanceof UpdateProvider) {
695 databaseId = ((UpdateProvider) annotation).databaseId();
696 sqlCommandType = SqlCommandType.UPDATE;
697 } else if (annotation instanceof InsertProvider) {
698 databaseId = ((InsertProvider) annotation).databaseId();
699 sqlCommandType = SqlCommandType.INSERT;
700 } else if (annotation instanceof DeleteProvider) {
701 databaseId = ((DeleteProvider) annotation).databaseId();
702 sqlCommandType = SqlCommandType.DELETE;
703 } else {
704 sqlCommandType = SqlCommandType.UNKNOWN;
705 if (annotation instanceof Options) {
706 databaseId = ((Options) annotation).databaseId();
707 } else if (annotation instanceof SelectKey) {
708 databaseId = ((SelectKey) annotation).databaseId();
709 } else {
710 databaseId = "";
711 }
712 }
713 }
714
715 Annotation getAnnotation() {
716 return annotation;
717 }
718
719 SqlCommandType getSqlCommandType() {
720 return sqlCommandType;
721 }
722
723 String getDatabaseId() {
724 return databaseId;
725 }
726 }
727 }