View Javadoc
1   /**
2    *    Copyright 2009-2019 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.builder.annotation;
17  
18  import java.io.IOException;
19  import java.io.InputStream;
20  import java.lang.annotation.Annotation;
21  import java.lang.reflect.Array;
22  import java.lang.reflect.GenericArrayType;
23  import java.lang.reflect.Method;
24  import java.lang.reflect.ParameterizedType;
25  import java.lang.reflect.Type;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.Locale;
33  import java.util.Map;
34  import java.util.Optional;
35  import java.util.Properties;
36  import java.util.Set;
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.BindingException;
62  import org.apache.ibatis.binding.MapperMethod.ParamMap;
63  import org.apache.ibatis.builder.BuilderException;
64  import org.apache.ibatis.builder.CacheRefResolver;
65  import org.apache.ibatis.builder.IncompleteElementException;
66  import org.apache.ibatis.builder.MapperBuilderAssistant;
67  import org.apache.ibatis.builder.xml.XMLMapperBuilder;
68  import org.apache.ibatis.cursor.Cursor;
69  import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
70  import org.apache.ibatis.executor.keygen.KeyGenerator;
71  import org.apache.ibatis.executor.keygen.NoKeyGenerator;
72  import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
73  import org.apache.ibatis.io.Resources;
74  import org.apache.ibatis.mapping.Discriminator;
75  import org.apache.ibatis.mapping.FetchType;
76  import org.apache.ibatis.mapping.MappedStatement;
77  import org.apache.ibatis.mapping.ResultFlag;
78  import org.apache.ibatis.mapping.ResultMapping;
79  import org.apache.ibatis.mapping.ResultSetType;
80  import org.apache.ibatis.mapping.SqlCommandType;
81  import org.apache.ibatis.mapping.SqlSource;
82  import org.apache.ibatis.mapping.StatementType;
83  import org.apache.ibatis.parsing.PropertyParser;
84  import org.apache.ibatis.reflection.TypeParameterResolver;
85  import org.apache.ibatis.scripting.LanguageDriver;
86  import org.apache.ibatis.session.Configuration;
87  import org.apache.ibatis.session.ResultHandler;
88  import org.apache.ibatis.session.RowBounds;
89  import org.apache.ibatis.type.JdbcType;
90  import org.apache.ibatis.type.TypeHandler;
91  import org.apache.ibatis.type.UnknownTypeHandler;
92  
93  /**
94   * @author Clinton Begin
95   * @author Kazuki Shimizu
96   */
97  public class MapperAnnotationBuilder {
98  
99    private static final Set<Class<? extends Annotation>> SQL_ANNOTATION_TYPES = new HashSet<>();
100   private static final Set<Class<? extends Annotation>> SQL_PROVIDER_ANNOTATION_TYPES = new HashSet<>();
101 
102   private final Configuration configuration;
103   private final MapperBuilderAssistant assistant;
104   private final Class<?> type;
105 
106   static {
107     SQL_ANNOTATION_TYPES.add(Select.class);
108     SQL_ANNOTATION_TYPES.add(Insert.class);
109     SQL_ANNOTATION_TYPES.add(Update.class);
110     SQL_ANNOTATION_TYPES.add(Delete.class);
111 
112     SQL_PROVIDER_ANNOTATION_TYPES.add(SelectProvider.class);
113     SQL_PROVIDER_ANNOTATION_TYPES.add(InsertProvider.class);
114     SQL_PROVIDER_ANNOTATION_TYPES.add(UpdateProvider.class);
115     SQL_PROVIDER_ANNOTATION_TYPES.add(DeleteProvider.class);
116   }
117 
118   public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
119     String resource = type.getName().replace('.', '/') + ".java (best guess)";
120     this.assistant = new MapperBuilderAssistant(configuration, resource);
121     this.configuration = configuration;
122     this.type = type;
123   }
124 
125   public void parse() {
126     String resource = type.toString();
127     if (!configuration.isResourceLoaded(resource)) {
128       loadXmlResource();
129       configuration.addLoadedResource(resource);
130       assistant.setCurrentNamespace(type.getName());
131       parseCache();
132       parseCacheRef();
133       Method[] methods = type.getMethods();
134       for (Method method : methods) {
135         try {
136           // issue #237
137           if (!method.isBridge()) {
138             parseStatement(method);
139           }
140         } catch (IncompleteElementException e) {
141           configuration.addIncompleteMethod(new MethodResolver(this, method));
142         }
143       }
144     }
145     parsePendingMethods();
146   }
147 
148   private void parsePendingMethods() {
149     Collection<MethodResolver> incompleteMethods = configuration.getIncompleteMethods();
150     synchronized (incompleteMethods) {
151       Iterator<MethodResolver> iter = incompleteMethods.iterator();
152       while (iter.hasNext()) {
153         try {
154           iter.next().resolve();
155           iter.remove();
156         } catch (IncompleteElementException e) {
157           // This method is still missing a resource
158         }
159       }
160     }
161   }
162 
163   private void loadXmlResource() {
164     // Spring may not know the real resource name so we check a flag
165     // to prevent loading again a resource twice
166     // this flag is set at XMLMapperBuilder#bindMapperForNamespace
167     if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
168       String xmlResource = type.getName().replace('.', '/') + ".xml";
169       // #1347
170       InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
171       if (inputStream == null) {
172         // Search XML mapper that is not in the module but in the classpath.
173         try {
174           inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
175         } catch (IOException e2) {
176           // ignore, resource is not required
177         }
178       }
179       if (inputStream != null) {
180         XMLMapperBuilderrBuilder.html#XMLMapperBuilder">XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
181         xmlParser.parse();
182       }
183     }
184   }
185 
186   private void parseCache() {
187     CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
188     if (cacheDomain != null) {
189       Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
190       Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
191       Properties props = convertToProperties(cacheDomain.properties());
192       assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, 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(),
203           PropertyParser.parse(property.value(), configuration.getVariables()));
204     }
205     return props;
206   }
207 
208   private void parseCacheRef() {
209     CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
210     if (cacheDomainRef != null) {
211       Class<?> refType = cacheDomainRef.value();
212       String refName = cacheDomainRef.name();
213       if (refType == void.class && refName.isEmpty()) {
214         throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
215       }
216       if (refType != void.class && !refName.isEmpty()) {
217         throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
218       }
219       String namespace = (refType != void.class) ? refType.getName() : refName;
220       try {
221         assistant.useCacheRef(namespace);
222       } catch (IncompleteElementException e) {
223         configuration.addIncompleteCacheRef(new CacheRefResolver(assistant, namespace));
224       }
225     }
226   }
227 
228   private String parseResultMap(Method method) {
229     Class<?> returnType = getReturnType(method);
230     Arg[] args = method.getAnnotationsByType(Arg.class);
231     Result[] results = method.getAnnotationsByType(Result.class);
232     TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
233     String resultMapId = generateResultMapName(method);
234     applyResultMap(resultMapId, returnType, args, results, typeDiscriminator);
235     return resultMapId;
236   }
237 
238   private String generateResultMapName(Method method) {
239     Results results = method.getAnnotation(Results.class);
240     if (results != null && !results.id().isEmpty()) {
241       return type.getName() + "." + results.id();
242     }
243     StringBuilder suffix = new StringBuilder();
244     for (Class<?> c : method.getParameterTypes()) {
245       suffix.append("-");
246       suffix.append(c.getSimpleName());
247     }
248     if (suffix.length() < 1) {
249       suffix.append("-void");
250     }
251     return type.getName() + "." + method.getName() + suffix;
252   }
253 
254   private void applyResultMap(String resultMapId, Class<?> returnType, Arg[] args, Result[] results, 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     // TODO add AutoMappingBehaviour
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         // issue #136
270         applyConstructorArgs(c.constructArgs(), resultType, resultMappings);
271         applyResults(c.results(), resultType, resultMappings);
272         // TODO add AutoMappingBehaviour
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<?>>)
285               (discriminator.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     Class<?> parameterTypeClass = getParameterType(method);
300     LanguageDriver languageDriver = getLanguageDriver(method);
301     SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
302     if (sqlSource != null) {
303       Options options = method.getAnnotation(Options.class);
304       final String mappedStatementId = type.getName() + "." + method.getName();
305       Integer fetchSize = null;
306       Integer timeout = null;
307       StatementType statementType = StatementType.PREPARED;
308       ResultSetType resultSetType = configuration.getDefaultResultSetType();
309       SqlCommandType sqlCommandType = getSqlCommandType(method);
310       boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
311       boolean flushCache = !isSelect;
312       boolean useCache = isSelect;
313 
314       KeyGenerator keyGenerator;
315       String keyProperty = null;
316       String keyColumn = null;
317       if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
318         // first check for SelectKey annotation - that overrides everything else
319         SelectKey selectKey = method.getAnnotation(SelectKey.class);
320         if (selectKey != null) {
321           keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
322           keyProperty = selectKey.keyProperty();
323         } else if (options == null) {
324           keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
325         } else {
326           keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
327           keyProperty = options.keyProperty();
328           keyColumn = options.keyColumn();
329         }
330       } else {
331         keyGenerator = NoKeyGenerator.INSTANCE;
332       }
333 
334       if (options != null) {
335         if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
336           flushCache = true;
337         } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
338           flushCache = false;
339         }
340         useCache = options.useCache();
341         fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
342         timeout = options.timeout() > -1 ? options.timeout() : null;
343         statementType = options.statementType();
344         if (options.resultSetType() != ResultSetType.DEFAULT) {
345           resultSetType = options.resultSetType();
346         }
347       }
348 
349       String resultMapId = null;
350       ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
351       if (resultMapAnnotation != null) {
352         resultMapId = String.join(",", resultMapAnnotation.value());
353       } else if (isSelect) {
354         resultMapId = parseResultMap(method);
355       }
356 
357       assistant.addMappedStatement(
358           mappedStatementId,
359           sqlSource,
360           statementType,
361           sqlCommandType,
362           fetchSize,
363           timeout,
364           // ParameterMapID
365           null,
366           parameterTypeClass,
367           resultMapId,
368           getReturnType(method),
369           resultSetType,
370           flushCache,
371           useCache,
372           // TODO gcode issue #577
373           false,
374           keyGenerator,
375           keyProperty,
376           keyColumn,
377           // DatabaseID
378           null,
379           languageDriver,
380           // ResultSets
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           // issue #135
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       // gcode issue #508
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             // (gcode issue #443) actual type can be a also a parameterized type
436             returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
437           } else if (returnTypeParameter instanceof GenericArrayType) {
438             Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
439             // (gcode issue #525) support List<byte[]>
440             returnType = Array.newInstance(componentType, 0).getClass();
441           }
442         }
443       } else if (method.isAnnotationPresent(MapKey.class) && Map.class.isAssignableFrom(rawType)) {
444         // (gcode issue 504) Do not look into Maps if there is not MapKey annotation
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             // (gcode issue 443) actual type can be a also a parameterized type
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 SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
468     try {
469       Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
470       Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
471       if (sqlAnnotationType != null) {
472         if (sqlProviderAnnotationType != null) {
473           throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
474         }
475         Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
476         final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
477         return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
478       } else if (sqlProviderAnnotationType != null) {
479         Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
480         return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
481       }
482       return null;
483     } catch (Exception e) {
484       throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
485     }
486   }
487 
488   private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
489     final StringBuilder sql = new StringBuilder();
490     for (String fragment : strings) {
491       sql.append(fragment);
492       sql.append(" ");
493     }
494     return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
495   }
496 
497   private SqlCommandType getSqlCommandType(Method method) {
498     Class<? extends Annotation> type = getSqlAnnotationType(method);
499 
500     if (type == null) {
501       type = getSqlProviderAnnotationType(method);
502 
503       if (type == null) {
504         return SqlCommandType.UNKNOWN;
505       }
506 
507       if (type == SelectProvider.class) {
508         type = Select.class;
509       } else if (type == InsertProvider.class) {
510         type = Insert.class;
511       } else if (type == UpdateProvider.class) {
512         type = Update.class;
513       } else if (type == DeleteProvider.class) {
514         type = Delete.class;
515       }
516     }
517 
518     return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH));
519   }
520 
521   private Class<? extends Annotation> getSqlAnnotationType(Method method) {
522     return chooseAnnotationType(method, SQL_ANNOTATION_TYPES);
523   }
524 
525   private Class<? extends Annotation> getSqlProviderAnnotationType(Method method) {
526     return chooseAnnotationType(method, SQL_PROVIDER_ANNOTATION_TYPES);
527   }
528 
529   private Class<? extends Annotation> chooseAnnotationType(Method method, Set<Class<? extends Annotation>> types) {
530     for (Class<? extends Annotation> type : types) {
531       Annotation annotation = method.getAnnotation(type);
532       if (annotation != null) {
533         return type;
534       }
535     }
536     return null;
537   }
538 
539   private void applyResults(Result[] results, Class<?> resultType, List<ResultMapping> resultMappings) {
540     for (Result result : results) {
541       List<ResultFlag> flags = new ArrayList<>();
542       if (result.id()) {
543         flags.add(ResultFlag.ID);
544       }
545       @SuppressWarnings("unchecked")
546       Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>)
547               ((result.typeHandler() == UnknownTypeHandler.class) ? null : result.typeHandler());
548       ResultMapping resultMapping = assistant.buildResultMapping(
549           resultType,
550           nullOrEmpty(result.property()),
551           nullOrEmpty(result.column()),
552           result.javaType() == void.class ? null : result.javaType(),
553           result.jdbcType() == JdbcType.UNDEFINED ? null : result.jdbcType(),
554           hasNestedSelect(result) ? nestedSelectId(result) : null,
555           null,
556           null,
557           null,
558           typeHandler,
559           flags,
560           null,
561           null,
562           isLazy(result));
563       resultMappings.add(resultMapping);
564     }
565   }
566 
567   private String nestedSelectId(Result result) {
568     String nestedSelect = result.one().select();
569     if (nestedSelect.length() < 1) {
570       nestedSelect = result.many().select();
571     }
572     if (!nestedSelect.contains(".")) {
573       nestedSelect = type.getName() + "." + nestedSelect;
574     }
575     return nestedSelect;
576   }
577 
578   private boolean isLazy(Result result) {
579     boolean isLazy = configuration.isLazyLoadingEnabled();
580     if (result.one().select().length() > 0 && FetchType.DEFAULT != result.one().fetchType()) {
581       isLazy = result.one().fetchType() == FetchType.LAZY;
582     } else if (result.many().select().length() > 0 && FetchType.DEFAULT != result.many().fetchType()) {
583       isLazy = result.many().fetchType() == FetchType.LAZY;
584     }
585     return isLazy;
586   }
587 
588   private boolean hasNestedSelect(Result result) {
589     if (result.one().select().length() > 0 && result.many().select().length() > 0) {
590       throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
591     }
592     return result.one().select().length() > 0 || result.many().select().length() > 0;
593   }
594 
595   private void applyConstructorArgs(Arg[] args, Class<?> resultType, List<ResultMapping> resultMappings) {
596     for (Arg arg : args) {
597       List<ResultFlag> flags = new ArrayList<>();
598       flags.add(ResultFlag.CONSTRUCTOR);
599       if (arg.id()) {
600         flags.add(ResultFlag.ID);
601       }
602       @SuppressWarnings("unchecked")
603       Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>)
604               (arg.typeHandler() == UnknownTypeHandler.class ? null : arg.typeHandler());
605       ResultMapping resultMapping = assistant.buildResultMapping(
606           resultType,
607           nullOrEmpty(arg.name()),
608           nullOrEmpty(arg.column()),
609           arg.javaType() == void.class ? null : arg.javaType(),
610           arg.jdbcType() == JdbcType.UNDEFINED ? null : arg.jdbcType(),
611           nullOrEmpty(arg.select()),
612           nullOrEmpty(arg.resultMap()),
613           null,
614           nullOrEmpty(arg.columnPrefix()),
615           typeHandler,
616           flags,
617           null,
618           null,
619           false);
620       resultMappings.add(resultMapping);
621     }
622   }
623 
624   private String nullOrEmpty(String value) {
625     return value == null || value.trim().length() == 0 ? null : value;
626   }
627 
628   private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, String baseStatementId, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
629     String id = baseStatementId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
630     Class<?> resultTypeClass = selectKeyAnnotation.resultType();
631     StatementType statementType = selectKeyAnnotation.statementType();
632     String keyProperty = selectKeyAnnotation.keyProperty();
633     String keyColumn = selectKeyAnnotation.keyColumn();
634     boolean executeBefore = selectKeyAnnotation.before();
635 
636     // defaults
637     boolean useCache = false;
638     KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
639     Integer fetchSize = null;
640     Integer timeout = null;
641     boolean flushCache = false;
642     String parameterMap = null;
643     String resultMap = null;
644     ResultSetType resultSetTypeEnum = null;
645 
646     SqlSource sqlSource = buildSqlSourceFromStrings(selectKeyAnnotation.statement(), parameterTypeClass, languageDriver);
647     SqlCommandType sqlCommandType = SqlCommandType.SELECT;
648 
649     assistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum,
650         flushCache, useCache, false,
651         keyGenerator, keyProperty, keyColumn, null, languageDriver, null);
652 
653     id = assistant.applyCurrentNamespace(id, false);
654 
655     MappedStatement keyStatement = configuration.getMappedStatement(id, false);
656     SelectKeyGeneratorectKeyGenerator.html#SelectKeyGenerator">SelectKeyGenerator answer = new SelectKeyGenerator(keyStatement, executeBefore);
657     configuration.addKeyGenerator(id, answer);
658     return answer;
659   }
660 
661 }