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