View Javadoc
1   /**
2    *    Copyright 2009-2015 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.Set;
35  
36  import org.apache.ibatis.annotations.Arg;
37  import org.apache.ibatis.annotations.CacheNamespace;
38  import org.apache.ibatis.annotations.CacheNamespaceRef;
39  import org.apache.ibatis.annotations.Case;
40  import org.apache.ibatis.annotations.ConstructorArgs;
41  import org.apache.ibatis.annotations.Delete;
42  import org.apache.ibatis.annotations.DeleteProvider;
43  import org.apache.ibatis.annotations.Insert;
44  import org.apache.ibatis.annotations.InsertProvider;
45  import org.apache.ibatis.annotations.Lang;
46  import org.apache.ibatis.annotations.MapKey;
47  import org.apache.ibatis.annotations.Options;
48  import org.apache.ibatis.annotations.Result;
49  import org.apache.ibatis.annotations.ResultMap;
50  import org.apache.ibatis.annotations.ResultType;
51  import org.apache.ibatis.annotations.Results;
52  import org.apache.ibatis.annotations.Select;
53  import org.apache.ibatis.annotations.SelectKey;
54  import org.apache.ibatis.annotations.SelectProvider;
55  import org.apache.ibatis.annotations.TypeDiscriminator;
56  import org.apache.ibatis.annotations.Update;
57  import org.apache.ibatis.annotations.UpdateProvider;
58  import org.apache.ibatis.binding.BindingException;
59  import org.apache.ibatis.binding.MapperMethod.ParamMap;
60  import org.apache.ibatis.builder.BuilderException;
61  import org.apache.ibatis.builder.IncompleteElementException;
62  import org.apache.ibatis.builder.MapperBuilderAssistant;
63  import org.apache.ibatis.builder.xml.XMLMapperBuilder;
64  import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
65  import org.apache.ibatis.executor.keygen.KeyGenerator;
66  import org.apache.ibatis.executor.keygen.NoKeyGenerator;
67  import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
68  import org.apache.ibatis.io.Resources;
69  import org.apache.ibatis.mapping.Discriminator;
70  import org.apache.ibatis.mapping.FetchType;
71  import org.apache.ibatis.mapping.MappedStatement;
72  import org.apache.ibatis.mapping.ResultFlag;
73  import org.apache.ibatis.mapping.ResultMapping;
74  import org.apache.ibatis.mapping.ResultSetType;
75  import org.apache.ibatis.mapping.SqlCommandType;
76  import org.apache.ibatis.mapping.SqlSource;
77  import org.apache.ibatis.mapping.StatementType;
78  import org.apache.ibatis.scripting.LanguageDriver;
79  import org.apache.ibatis.session.Configuration;
80  import org.apache.ibatis.session.ResultHandler;
81  import org.apache.ibatis.session.RowBounds;
82  import org.apache.ibatis.type.JdbcType;
83  import org.apache.ibatis.type.TypeHandler;
84  import org.apache.ibatis.type.UnknownTypeHandler;
85  
86  /**
87   * @author Clinton Begin
88   */
89  public class MapperAnnotationBuilder {
90  
91    private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();
92    private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();
93  
94    private Configuration configuration;
95    private MapperBuilderAssistant assistant;
96    private Class<?> type;
97  
98    public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
99      String resource = type.getName().replace('.', '/') + ".java (best guess)";
100     this.assistant = new MapperBuilderAssistant(configuration, resource);
101     this.configuration = configuration;
102     this.type = type;
103 
104     sqlAnnotationTypes.add(Select.class);
105     sqlAnnotationTypes.add(Insert.class);
106     sqlAnnotationTypes.add(Update.class);
107     sqlAnnotationTypes.add(Delete.class);
108 
109     sqlProviderAnnotationTypes.add(SelectProvider.class);
110     sqlProviderAnnotationTypes.add(InsertProvider.class);
111     sqlProviderAnnotationTypes.add(UpdateProvider.class);
112     sqlProviderAnnotationTypes.add(DeleteProvider.class);
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       Method[] methods = type.getMethods();
124       for (Method method : methods) {
125         try {
126           // issue #237
127           if (!method.isBridge()) {
128             parseStatement(method);
129           }
130         } catch (IncompleteElementException e) {
131           configuration.addIncompleteMethod(new MethodResolver(this, method));
132         }
133       }
134     }
135     parsePendingMethods();
136   }
137 
138   private void parsePendingMethods() {
139     Collection<MethodResolver> incompleteMethods = configuration.getIncompleteMethods();
140     synchronized (incompleteMethods) {
141       Iterator<MethodResolver> iter = incompleteMethods.iterator();
142       while (iter.hasNext()) {
143         try {
144           iter.next().resolve();
145           iter.remove();
146         } catch (IncompleteElementException e) {
147           // This method is still missing a resource
148         }
149       }
150     }
151   }
152 
153   private void loadXmlResource() {
154     // Spring may not know the real resource name so we check a flag
155     // to prevent loading again a resource twice
156     // this flag is set at XMLMapperBuilder#bindMapperForNamespace
157     if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
158       String xmlResource = type.getName().replace('.', '/') + ".xml";
159       InputStream inputStream = null;
160       try {
161         inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
162       } catch (IOException e) {
163         // ignore, resource is not required
164       }
165       if (inputStream != null) {
166         XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
167         xmlParser.parse();
168       }
169     }
170   }
171 
172   private void parseCache() {
173     CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
174     if (cacheDomain != null) {
175       Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
176       Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
177       assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), null);
178     }
179   }
180 
181   private void parseCacheRef() {
182     CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
183     if (cacheDomainRef != null) {
184       assistant.useCacheRef(cacheDomainRef.value().getName());
185     }
186   }
187 
188   private String parseResultMap(Method method) {
189     Class<?> returnType = getReturnType(method);
190     ConstructorArgs args = method.getAnnotation(ConstructorArgs.class);
191     Results results = method.getAnnotation(Results.class);
192     TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
193     String resultMapId = generateResultMapName(method);
194     applyResultMap(resultMapId, returnType, argsIf(args), resultsIf(results), typeDiscriminator);
195     return resultMapId;
196   }
197 
198   private String generateResultMapName(Method method) {
199     Results results = method.getAnnotation(Results.class);
200     if (results != null && !results.id().isEmpty()) {
201       return type.getName() + "." + results.id();
202     }
203     StringBuilder suffix = new StringBuilder();
204     for (Class<?> c : method.getParameterTypes()) {
205       suffix.append("-");
206       suffix.append(c.getSimpleName());
207     }
208     if (suffix.length() < 1) {
209       suffix.append("-void");
210     }
211     return type.getName() + "." + method.getName() + suffix;
212   }
213 
214   private void applyResultMap(String resultMapId, Class<?> returnType, Arg[] args, Result[] results, TypeDiscriminator discriminator) {
215     List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
216     applyConstructorArgs(args, returnType, resultMappings);
217     applyResults(results, returnType, resultMappings);
218     Discriminator disc = applyDiscriminator(resultMapId, returnType, discriminator);
219     // TODO add AutoMappingBehaviour
220     assistant.addResultMap(resultMapId, returnType, null, disc, resultMappings, null);
221     createDiscriminatorResultMaps(resultMapId, returnType, discriminator);
222   }
223 
224   private void createDiscriminatorResultMaps(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
225     if (discriminator != null) {
226       for (Case c : discriminator.cases()) {
227         String caseResultMapId = resultMapId + "-" + c.value();
228         List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
229         // issue #136
230         applyConstructorArgs(c.constructArgs(), resultType, resultMappings);
231         applyResults(c.results(), resultType, resultMappings);
232         // TODO add AutoMappingBehaviour
233         assistant.addResultMap(caseResultMapId, c.type(), resultMapId, null, resultMappings, null);
234       }
235     }
236   }
237 
238   private Discriminator applyDiscriminator(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
239     if (discriminator != null) {
240       String column = discriminator.column();
241       Class<?> javaType = discriminator.javaType() == void.class ? String.class : discriminator.javaType();
242       JdbcType jdbcType = discriminator.jdbcType() == JdbcType.UNDEFINED ? null : discriminator.jdbcType();
243       Class<? extends TypeHandler<?>> typeHandler = discriminator.typeHandler() == UnknownTypeHandler.class ? null : discriminator.typeHandler();
244       Case[] cases = discriminator.cases();
245       Map<String, String> discriminatorMap = new HashMap<String, String>();
246       for (Case c : cases) {
247         String value = c.value();
248         String caseResultMapId = resultMapId + "-" + value;
249         discriminatorMap.put(value, caseResultMapId);
250       }
251       return assistant.buildDiscriminator(resultType, column, javaType, jdbcType, typeHandler, discriminatorMap);
252     }
253     return null;
254   }
255 
256   void parseStatement(Method method) {
257     Class<?> parameterTypeClass = getParameterType(method);
258     LanguageDriver languageDriver = getLanguageDriver(method);
259     SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
260     if (sqlSource != null) {
261       Options options = method.getAnnotation(Options.class);
262       final String mappedStatementId = type.getName() + "." + method.getName();
263       Integer fetchSize = null;
264       Integer timeout = null;
265       StatementType statementType = StatementType.PREPARED;
266       ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
267       SqlCommandType sqlCommandType = getSqlCommandType(method);
268       boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
269       boolean flushCache = !isSelect;
270       boolean useCache = isSelect;
271 
272       KeyGenerator keyGenerator;
273       String keyProperty = "id";
274       String keyColumn = null;
275       if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
276         // first check for SelectKey annotation - that overrides everything else
277         SelectKey selectKey = method.getAnnotation(SelectKey.class);
278         if (selectKey != null) {
279           keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
280           keyProperty = selectKey.keyProperty();
281         } else if (options == null) {
282           keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
283         } else {
284           keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
285           keyProperty = options.keyProperty();
286           keyColumn = options.keyColumn();
287         }
288       } else {
289         keyGenerator = new NoKeyGenerator();
290       }
291 
292       if (options != null) {
293         flushCache = options.flushCache();
294         useCache = options.useCache();
295         fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
296         timeout = options.timeout() > -1 ? options.timeout() : null;
297         statementType = options.statementType();
298         resultSetType = options.resultSetType();
299       }
300 
301       String resultMapId = null;
302       ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
303       if (resultMapAnnotation != null) {
304         String[] resultMaps = resultMapAnnotation.value();
305         StringBuilder sb = new StringBuilder();
306         for (String resultMap : resultMaps) {
307           if (sb.length() > 0) {
308             sb.append(",");
309           }
310           sb.append(resultMap);
311         }
312         resultMapId = sb.toString();
313       } else if (isSelect) {
314         resultMapId = parseResultMap(method);
315       }
316 
317       assistant.addMappedStatement(
318           mappedStatementId,
319           sqlSource,
320           statementType,
321           sqlCommandType,
322           fetchSize,
323           timeout,
324           // ParameterMapID
325           null,
326           parameterTypeClass,
327           resultMapId,
328           getReturnType(method),
329           resultSetType,
330           flushCache,
331           useCache,
332           // TODO issue #577
333           false,
334           keyGenerator,
335           keyProperty,
336           keyColumn,
337           // DatabaseID
338           null,
339           languageDriver,
340           // ResultSets
341           null);
342     }
343   }
344   
345   private LanguageDriver getLanguageDriver(Method method) {
346     Lang lang = method.getAnnotation(Lang.class);
347     Class<?> langClass = null;
348     if (lang != null) {
349       langClass = lang.value();
350     }
351     return assistant.getLanguageDriver(langClass);
352   }
353 
354   private Class<?> getParameterType(Method method) {
355     Class<?> parameterType = null;
356     Class<?>[] parameterTypes = method.getParameterTypes();
357     for (int i = 0; i < parameterTypes.length; i++) {
358       if (!RowBounds.class.isAssignableFrom(parameterTypes[i]) && !ResultHandler.class.isAssignableFrom(parameterTypes[i])) {
359         if (parameterType == null) {
360           parameterType = parameterTypes[i];
361         } else {
362           // issue #135
363           parameterType = ParamMap.class;
364         }
365       }
366     }
367     return parameterType;
368   }
369 
370   private Class<?> getReturnType(Method method) {
371     Class<?> returnType = method.getReturnType();
372     // issue #508
373     if (void.class.equals(returnType)) {
374       ResultType rt = method.getAnnotation(ResultType.class);
375       if (rt != null) {
376         returnType = rt.value();
377       } 
378     } else if (Collection.class.isAssignableFrom(returnType)) {
379       Type returnTypeParameter = method.getGenericReturnType();
380       if (returnTypeParameter instanceof ParameterizedType) {
381         Type[] actualTypeArguments = ((ParameterizedType) returnTypeParameter).getActualTypeArguments();
382         if (actualTypeArguments != null && actualTypeArguments.length == 1) {
383           returnTypeParameter = actualTypeArguments[0];
384           if (returnTypeParameter instanceof Class) {
385             returnType = (Class<?>) returnTypeParameter;
386           } else if (returnTypeParameter instanceof ParameterizedType) {
387             // (issue #443) actual type can be a also a parameterized type
388             returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
389           } else if (returnTypeParameter instanceof GenericArrayType) {
390             Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
391             // (issue #525) support List<byte[]>
392             returnType = Array.newInstance(componentType, 0).getClass();
393           }
394         }
395       }
396     } else if (method.isAnnotationPresent(MapKey.class) && Map.class.isAssignableFrom(returnType)) {
397       // (issue 504) Do not look into Maps if there is not MapKey annotation
398       Type returnTypeParameter = method.getGenericReturnType();
399       if (returnTypeParameter instanceof ParameterizedType) {
400         Type[] actualTypeArguments = ((ParameterizedType) returnTypeParameter).getActualTypeArguments();
401         if (actualTypeArguments != null && actualTypeArguments.length == 2) {
402           returnTypeParameter = actualTypeArguments[1];
403           if (returnTypeParameter instanceof Class) {
404             returnType = (Class<?>) returnTypeParameter;
405           } else if (returnTypeParameter instanceof ParameterizedType) {
406             // (issue 443) actual type can be a also a parameterized type
407             returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
408           }
409         }
410       }
411     }
412 
413     return returnType;
414   }
415 
416   private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
417     try {
418       Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
419       Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
420       if (sqlAnnotationType != null) {
421         if (sqlProviderAnnotationType != null) {
422           throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
423         }
424         Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
425         final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
426         return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
427       } else if (sqlProviderAnnotationType != null) {
428         Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
429         return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
430       }
431       return null;
432     } catch (Exception e) {
433       throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
434     }
435   }
436 
437   private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
438     final StringBuilder sql = new StringBuilder();
439     for (String fragment : strings) {
440       sql.append(fragment);
441       sql.append(" ");
442     }
443     return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
444   }
445 
446   private SqlCommandType getSqlCommandType(Method method) {
447     Class<? extends Annotation> type = getSqlAnnotationType(method);
448 
449     if (type == null) {
450       type = getSqlProviderAnnotationType(method);
451 
452       if (type == null) {
453         return SqlCommandType.UNKNOWN;
454       }
455 
456       if (type == SelectProvider.class) {
457         type = Select.class;
458       } else if (type == InsertProvider.class) {
459         type = Insert.class;
460       } else if (type == UpdateProvider.class) {
461         type = Update.class;
462       } else if (type == DeleteProvider.class) {
463         type = Delete.class;
464       }
465     }
466 
467     return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH));
468   }
469 
470   private Class<? extends Annotation> getSqlAnnotationType(Method method) {
471     return chooseAnnotationType(method, sqlAnnotationTypes);
472   }
473 
474   private Class<? extends Annotation> getSqlProviderAnnotationType(Method method) {
475     return chooseAnnotationType(method, sqlProviderAnnotationTypes);
476   }
477 
478   private Class<? extends Annotation> chooseAnnotationType(Method method, Set<Class<? extends Annotation>> types) {
479     for (Class<? extends Annotation> type : types) {
480       Annotation annotation = method.getAnnotation(type);
481       if (annotation != null) {
482         return type;
483       }
484     }
485     return null;
486   }
487 
488   private void applyResults(Result[] results, Class<?> resultType, List<ResultMapping> resultMappings) {
489     for (Result result : results) {
490       List<ResultFlag> flags = new ArrayList<ResultFlag>();
491       if (result.id()) {
492         flags.add(ResultFlag.ID);
493       }
494       ResultMapping resultMapping = assistant.buildResultMapping(
495           resultType,
496           nullOrEmpty(result.property()),
497           nullOrEmpty(result.column()),
498           result.javaType() == void.class ? null : result.javaType(),
499           result.jdbcType() == JdbcType.UNDEFINED ? null : result.jdbcType(),
500           hasNestedSelect(result) ? nestedSelectId(result) : null,
501           null,
502           null,
503           null,
504           result.typeHandler() == UnknownTypeHandler.class ? null : result.typeHandler(),
505           flags,
506           null,
507           null,
508           isLazy(result));
509       resultMappings.add(resultMapping);
510     }
511   }
512 
513   private String nestedSelectId(Result result) {
514     String nestedSelect = result.one().select();
515     if (nestedSelect.length() < 1) {
516       nestedSelect = result.many().select();
517     }
518     if (!nestedSelect.contains(".")) {
519       nestedSelect = type.getName() + "." + nestedSelect;
520     }
521     return nestedSelect;
522   }
523 
524   private boolean isLazy(Result result) {
525     boolean isLazy = configuration.isLazyLoadingEnabled();
526     if (result.one().select().length() > 0 && FetchType.DEFAULT != result.one().fetchType()) {
527       isLazy = (result.one().fetchType() == FetchType.LAZY);
528     } else if (result.many().select().length() > 0 && FetchType.DEFAULT != result.many().fetchType()) {
529       isLazy = (result.many().fetchType() == FetchType.LAZY);
530     }
531     return isLazy;
532   }
533   
534   private boolean hasNestedSelect(Result result) {
535     if (result.one().select().length() > 0 && result.many().select().length() > 0) {
536       throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
537     }
538     return result.one().select().length() > 0 || result.many().select().length() > 0;  
539   }
540 
541   private void applyConstructorArgs(Arg[] args, Class<?> resultType, List<ResultMapping> resultMappings) {
542     for (Arg arg : args) {
543       List<ResultFlag> flags = new ArrayList<ResultFlag>();
544       flags.add(ResultFlag.CONSTRUCTOR);
545       if (arg.id()) {
546         flags.add(ResultFlag.ID);
547       }
548       ResultMapping resultMapping = assistant.buildResultMapping(
549           resultType,
550           null,
551           nullOrEmpty(arg.column()),
552           arg.javaType() == void.class ? null : arg.javaType(),
553           arg.jdbcType() == JdbcType.UNDEFINED ? null : arg.jdbcType(),
554           nullOrEmpty(arg.select()),
555           nullOrEmpty(arg.resultMap()),
556           null,
557           null,
558           arg.typeHandler() == UnknownTypeHandler.class ? null : arg.typeHandler(),
559           flags,
560           null,
561           null,
562           false);
563       resultMappings.add(resultMapping);
564     }
565   }
566 
567   private String nullOrEmpty(String value) {
568     return value == null || value.trim().length() == 0 ? null : value;
569   }
570 
571   private Result[] resultsIf(Results results) {
572     return results == null ? new Result[0] : results.value();
573   }
574 
575   private Arg[] argsIf(ConstructorArgs args) {
576     return args == null ? new Arg[0] : args.value();
577   }
578 
579   private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, String baseStatementId, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
580     String id = baseStatementId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
581     Class<?> resultTypeClass = selectKeyAnnotation.resultType();
582     StatementType statementType = selectKeyAnnotation.statementType();
583     String keyProperty = selectKeyAnnotation.keyProperty();
584     String keyColumn = selectKeyAnnotation.keyColumn();
585     boolean executeBefore = selectKeyAnnotation.before();
586 
587     // defaults
588     boolean useCache = false;
589     KeyGenerator keyGenerator = new NoKeyGenerator();
590     Integer fetchSize = null;
591     Integer timeout = null;
592     boolean flushCache = false;
593     String parameterMap = null;
594     String resultMap = null;
595     ResultSetType resultSetTypeEnum = null;
596 
597     SqlSource sqlSource = buildSqlSourceFromStrings(selectKeyAnnotation.statement(), parameterTypeClass, languageDriver);
598     SqlCommandType sqlCommandType = SqlCommandType.SELECT;
599 
600     assistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum,
601         flushCache, useCache, false,
602         keyGenerator, keyProperty, keyColumn, null, languageDriver, null);
603 
604     id = assistant.applyCurrentNamespace(id, false);
605 
606     MappedStatement keyStatement = configuration.getMappedStatement(id, false);
607     SelectKeyGenerator answer = new SelectKeyGenerator(keyStatement, executeBefore);
608     configuration.addKeyGenerator(id, answer);
609     return answer;
610   }
611 
612 }