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 }