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