org.apache.ibatis.builder.annotation.MapperAnnotationBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mybatis Show documentation
Show all versions of mybatis Show documentation
The MyBatis SQL mapper framework makes it easier to use a relational database with object-oriented
applications. MyBatis couples objects with stored procedures or SQL statements using a XML descriptor or
annotations. Simplicity is the biggest advantage of the MyBatis data mapper over object relational mapping
tools.
/*
* Copyright 2009-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.builder.annotation;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.ibatis.annotations.Arg;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.CacheNamespaceRef;
import org.apache.ibatis.annotations.Case;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.Lang;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Options.FlushCachePolicy;
import org.apache.ibatis.annotations.Property;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.ResultType;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectKey;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.TypeDiscriminator;
import org.apache.ibatis.annotations.Update;
import org.apache.ibatis.annotations.UpdateProvider;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.CacheRefResolver;
import org.apache.ibatis.builder.IncompleteElementException;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Discriminator;
import org.apache.ibatis.mapping.FetchType;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.mapping.ResultSetType;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.parsing.PropertyParser;
import org.apache.ibatis.reflection.TypeParameterResolver;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.UnknownTypeHandler;
/**
* @author Clinton Begin
* @author Kazuki Shimizu
*/
public class MapperAnnotationBuilder {
private static final Set> statementAnnotationTypes = Stream
.of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, UpdateProvider.class,
InsertProvider.class, DeleteProvider.class)
.collect(Collectors.toSet());
private final Configuration configuration;
private final MapperBuilderAssistant assistant;
private final Class> type;
public MapperAnnotationBuilder(Configuration configuration, Class> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
private static boolean canHaveStatement(Method method) {
// issue #237
return !method.isBridge() && !method.isDefault();
}
private void parsePendingMethods() {
Collection incompleteMethods = configuration.getIncompleteMethods();
synchronized (incompleteMethods) {
Iterator iter = incompleteMethods.iterator();
while (iter.hasNext()) {
try {
iter.next().resolve();
iter.remove();
} catch (IncompleteElementException e) {
// This method is still missing a resource
}
}
}
}
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";
// #1347
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource,
configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
private void parseCache() {
CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
if (cacheDomain != null) {
Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
Properties props = convertToProperties(cacheDomain.properties());
assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size,
cacheDomain.readWrite(), cacheDomain.blocking(), props);
}
}
private Properties convertToProperties(Property[] properties) {
if (properties.length == 0) {
return null;
}
Properties props = new Properties();
for (Property property : properties) {
props.setProperty(property.name(), PropertyParser.parse(property.value(), configuration.getVariables()));
}
return props;
}
private void parseCacheRef() {
CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
if (cacheDomainRef != null) {
Class> refType = cacheDomainRef.value();
String refName = cacheDomainRef.name();
if (refType == void.class && refName.isEmpty()) {
throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
}
if (refType != void.class && !refName.isEmpty()) {
throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
}
String namespace = refType != void.class ? refType.getName() : refName;
try {
assistant.useCacheRef(namespace);
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(new CacheRefResolver(assistant, namespace));
}
}
}
private String parseResultMap(Method method) {
Class> returnType = getReturnType(method, type);
Arg[] args = method.getAnnotationsByType(Arg.class);
Result[] results = method.getAnnotationsByType(Result.class);
TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
String resultMapId = generateResultMapName(method);
applyResultMap(resultMapId, returnType, args, results, typeDiscriminator);
return resultMapId;
}
private String generateResultMapName(Method method) {
Results results = method.getAnnotation(Results.class);
if (results != null && !results.id().isEmpty()) {
return type.getName() + "." + results.id();
}
StringBuilder suffix = new StringBuilder();
for (Class> c : method.getParameterTypes()) {
suffix.append("-");
suffix.append(c.getSimpleName());
}
if (suffix.length() < 1) {
suffix.append("-void");
}
return type.getName() + "." + method.getName() + suffix;
}
private void applyResultMap(String resultMapId, Class> returnType, Arg[] args, Result[] results,
TypeDiscriminator discriminator) {
List resultMappings = new ArrayList<>();
applyConstructorArgs(args, returnType, resultMappings);
applyResults(results, returnType, resultMappings);
Discriminator disc = applyDiscriminator(resultMapId, returnType, discriminator);
// TODO add AutoMappingBehaviour
assistant.addResultMap(resultMapId, returnType, null, disc, resultMappings, null);
createDiscriminatorResultMaps(resultMapId, returnType, discriminator);
}
private void createDiscriminatorResultMaps(String resultMapId, Class> resultType, TypeDiscriminator discriminator) {
if (discriminator != null) {
for (Case c : discriminator.cases()) {
String caseResultMapId = resultMapId + "-" + c.value();
List resultMappings = new ArrayList<>();
// issue #136
applyConstructorArgs(c.constructArgs(), resultType, resultMappings);
applyResults(c.results(), resultType, resultMappings);
// TODO add AutoMappingBehaviour
assistant.addResultMap(caseResultMapId, c.type(), resultMapId, null, resultMappings, null);
}
}
}
private Discriminator applyDiscriminator(String resultMapId, Class> resultType, TypeDiscriminator discriminator) {
if (discriminator != null) {
String column = discriminator.column();
Class> javaType = discriminator.javaType() == void.class ? String.class : discriminator.javaType();
JdbcType jdbcType = discriminator.jdbcType() == JdbcType.UNDEFINED ? null : discriminator.jdbcType();
@SuppressWarnings("unchecked")
Class extends TypeHandler>> typeHandler = (Class extends TypeHandler>>) (discriminator
.typeHandler() == UnknownTypeHandler.class ? null : discriminator.typeHandler());
Case[] cases = discriminator.cases();
Map discriminatorMap = new HashMap<>();
for (Case c : cases) {
String value = c.value();
String caseResultMapId = resultMapId + "-" + value;
discriminatorMap.put(value, caseResultMapId);
}
return assistant.buildDiscriminator(resultType, column, javaType, jdbcType, typeHandler, discriminatorMap);
}
return null;
}
void parseStatement(Method method) {
final Class> parameterTypeClass = getParameterType(method);
final LanguageDriver languageDriver = getLanguageDriver(method);
getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {
final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass,
languageDriver, method);
final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
final Options options = getAnnotationWrapper(method, false, Options.class).map(x -> (Options) x.getAnnotation())
.orElse(null);
final String mappedStatementId = type.getName() + "." + method.getName();
final KeyGenerator keyGenerator;
String keyProperty = null;
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = getAnnotationWrapper(method, false, SelectKey.class)
.map(x -> (SelectKey) x.getAnnotation()).orElse(null);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method),
languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = configuration.getDefaultResultSetType();
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
// issue #348
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null;
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
if (options.resultSetType() != ResultSetType.DEFAULT) {
resultSetType = options.resultSetType();
}
}
String resultMapId = null;
if (isSelect) {
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
resultMapId = String.join(",", resultMapAnnotation.value());
} else {
resultMapId = generateResultMapName(method);
}
}
assistant.addMappedStatement(mappedStatementId, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
// ParameterMapID
null, parameterTypeClass, resultMapId, getReturnType(method, type), resultSetType, flushCache, useCache,
// TODO gcode issue #577
false, keyGenerator, keyProperty, keyColumn, statementAnnotation.getDatabaseId(), languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null, statementAnnotation.isDirtySelect());
});
}
private LanguageDriver getLanguageDriver(Method method) {
Lang lang = method.getAnnotation(Lang.class);
Class extends LanguageDriver> langClass = null;
if (lang != null) {
langClass = lang.value();
}
return configuration.getLanguageDriver(langClass);
}
private Class> getParameterType(Method method) {
Class> parameterType = null;
Class>[] parameterTypes = method.getParameterTypes();
for (Class> currentParameterType : parameterTypes) {
if (!RowBounds.class.isAssignableFrom(currentParameterType)
&& !ResultHandler.class.isAssignableFrom(currentParameterType)) {
if (parameterType == null) {
parameterType = currentParameterType;
} else {
// issue #135
parameterType = ParamMap.class;
}
}
}
return parameterType;
}
private static Class> getReturnType(Method method, Class> type) {
Class> returnType = method.getReturnType();
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, type);
if (resolvedReturnType instanceof Class) {
returnType = (Class>) resolvedReturnType;
if (returnType.isArray()) {
returnType = returnType.getComponentType();
}
// gcode issue #508
if (void.class.equals(returnType)) {
ResultType rt = method.getAnnotation(ResultType.class);
if (rt != null) {
returnType = rt.value();
}
}
} else if (resolvedReturnType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) resolvedReturnType;
Class> rawType = (Class>) parameterizedType.getRawType();
if (Collection.class.isAssignableFrom(rawType) || Cursor.class.isAssignableFrom(rawType)) {
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if (actualTypeArguments != null && actualTypeArguments.length == 1) {
Type returnTypeParameter = actualTypeArguments[0];
if (returnTypeParameter instanceof Class>) {
returnType = (Class>) returnTypeParameter;
} else if (returnTypeParameter instanceof ParameterizedType) {
// (gcode issue #443) actual type can be a also a parameterized type
returnType = (Class>) ((ParameterizedType) returnTypeParameter).getRawType();
} else if (returnTypeParameter instanceof GenericArrayType) {
Class> componentType = (Class>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
// (gcode issue #525) support List
returnType = Array.newInstance(componentType, 0).getClass();
}
}
} else if (method.isAnnotationPresent(MapKey.class) && Map.class.isAssignableFrom(rawType)) {
// (gcode issue 504) Do not look into Maps if there is not MapKey annotation
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if (actualTypeArguments != null && actualTypeArguments.length == 2) {
Type returnTypeParameter = actualTypeArguments[1];
if (returnTypeParameter instanceof Class>) {
returnType = (Class>) returnTypeParameter;
} else if (returnTypeParameter instanceof ParameterizedType) {
// (gcode issue 443) actual type can be a also a parameterized type
returnType = (Class>) ((ParameterizedType) returnTypeParameter).getRawType();
}
}
} else if (Optional.class.equals(rawType)) {
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
Type returnTypeParameter = actualTypeArguments[0];
if (returnTypeParameter instanceof Class>) {
returnType = (Class>) returnTypeParameter;
}
}
}
return returnType;
}
private void applyResults(Result[] results, Class> resultType, List resultMappings) {
for (Result result : results) {
List flags = new ArrayList<>();
if (result.id()) {
flags.add(ResultFlag.ID);
}
@SuppressWarnings("unchecked")
Class extends TypeHandler>> typeHandler = (Class extends TypeHandler>>) (result
.typeHandler() == UnknownTypeHandler.class ? null : result.typeHandler());
boolean hasNestedResultMap = hasNestedResultMap(result);
ResultMapping resultMapping = assistant.buildResultMapping(resultType, nullOrEmpty(result.property()),
nullOrEmpty(result.column()), result.javaType() == void.class ? null : result.javaType(),
result.jdbcType() == JdbcType.UNDEFINED ? null : result.jdbcType(),
hasNestedSelect(result) ? nestedSelectId(result) : null,
hasNestedResultMap ? nestedResultMapId(result) : null, null,
hasNestedResultMap ? findColumnPrefix(result) : null, typeHandler, flags, null, null, isLazy(result));
resultMappings.add(resultMapping);
}
}
private String findColumnPrefix(Result result) {
String columnPrefix = result.one().columnPrefix();
if (columnPrefix.length() < 1) {
columnPrefix = result.many().columnPrefix();
}
return columnPrefix;
}
private String nestedResultMapId(Result result) {
String resultMapId = result.one().resultMap();
if (resultMapId.length() < 1) {
resultMapId = result.many().resultMap();
}
if (!resultMapId.contains(".")) {
resultMapId = type.getName() + "." + resultMapId;
}
return resultMapId;
}
private boolean hasNestedResultMap(Result result) {
if (result.one().resultMap().length() > 0 && result.many().resultMap().length() > 0) {
throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
}
return result.one().resultMap().length() > 0 || result.many().resultMap().length() > 0;
}
private String nestedSelectId(Result result) {
String nestedSelect = result.one().select();
if (nestedSelect.length() < 1) {
nestedSelect = result.many().select();
}
if (!nestedSelect.contains(".")) {
nestedSelect = type.getName() + "." + nestedSelect;
}
return nestedSelect;
}
private boolean isLazy(Result result) {
boolean isLazy = configuration.isLazyLoadingEnabled();
if (result.one().select().length() > 0 && FetchType.DEFAULT != result.one().fetchType()) {
isLazy = result.one().fetchType() == FetchType.LAZY;
} else if (result.many().select().length() > 0 && FetchType.DEFAULT != result.many().fetchType()) {
isLazy = result.many().fetchType() == FetchType.LAZY;
}
return isLazy;
}
private boolean hasNestedSelect(Result result) {
if (result.one().select().length() > 0 && result.many().select().length() > 0) {
throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
}
return result.one().select().length() > 0 || result.many().select().length() > 0;
}
private void applyConstructorArgs(Arg[] args, Class> resultType, List resultMappings) {
for (Arg arg : args) {
List flags = new ArrayList<>();
flags.add(ResultFlag.CONSTRUCTOR);
if (arg.id()) {
flags.add(ResultFlag.ID);
}
@SuppressWarnings("unchecked")
Class extends TypeHandler>> typeHandler = (Class extends TypeHandler>>) (arg
.typeHandler() == UnknownTypeHandler.class ? null : arg.typeHandler());
ResultMapping resultMapping = assistant.buildResultMapping(resultType, nullOrEmpty(arg.name()),
nullOrEmpty(arg.column()), arg.javaType() == void.class ? null : arg.javaType(),
arg.jdbcType() == JdbcType.UNDEFINED ? null : arg.jdbcType(), nullOrEmpty(arg.select()),
nullOrEmpty(arg.resultMap()), null, nullOrEmpty(arg.columnPrefix()), typeHandler, flags, null, null, false);
resultMappings.add(resultMapping);
}
}
private String nullOrEmpty(String value) {
return value == null || value.trim().length() == 0 ? null : value;
}
private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, String baseStatementId,
Class> parameterTypeClass, LanguageDriver languageDriver) {
String id = baseStatementId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
Class> resultTypeClass = selectKeyAnnotation.resultType();
StatementType statementType = selectKeyAnnotation.statementType();
String keyProperty = selectKeyAnnotation.keyProperty();
String keyColumn = selectKeyAnnotation.keyColumn();
boolean executeBefore = selectKeyAnnotation.before();
// defaults
boolean useCache = false;
KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
Integer fetchSize = null;
Integer timeout = null;
boolean flushCache = false;
String parameterMap = null;
String resultMap = null;
ResultSetType resultSetTypeEnum = null;
String databaseId = selectKeyAnnotation.databaseId().isEmpty() ? null : selectKeyAnnotation.databaseId();
SqlSource sqlSource = buildSqlSource(selectKeyAnnotation, parameterTypeClass, languageDriver, null);
SqlCommandType sqlCommandType = SqlCommandType.SELECT;
assistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, false, keyGenerator,
keyProperty, keyColumn, databaseId, languageDriver, null, false);
id = assistant.applyCurrentNamespace(id, false);
MappedStatement keyStatement = configuration.getMappedStatement(id, false);
SelectKeyGenerator answer = new SelectKeyGenerator(keyStatement, executeBefore);
configuration.addKeyGenerator(id, answer);
return answer;
}
private SqlSource buildSqlSource(Annotation annotation, Class> parameterType, LanguageDriver languageDriver,
Method method) {
if (annotation instanceof Select) {
return buildSqlSourceFromStrings(((Select) annotation).value(), parameterType, languageDriver);
}
if (annotation instanceof Update) {
return buildSqlSourceFromStrings(((Update) annotation).value(), parameterType, languageDriver);
} else if (annotation instanceof Insert) {
return buildSqlSourceFromStrings(((Insert) annotation).value(), parameterType, languageDriver);
} else if (annotation instanceof Delete) {
return buildSqlSourceFromStrings(((Delete) annotation).value(), parameterType, languageDriver);
} else if (annotation instanceof SelectKey) {
return buildSqlSourceFromStrings(((SelectKey) annotation).statement(), parameterType, languageDriver);
}
return new ProviderSqlSource(assistant.getConfiguration(), annotation, type, method);
}
private SqlSource buildSqlSourceFromStrings(String[] strings, Class> parameterTypeClass,
LanguageDriver languageDriver) {
return languageDriver.createSqlSource(configuration, String.join(" ", strings).trim(), parameterTypeClass);
}
@SafeVarargs
private final Optional getAnnotationWrapper(Method method, boolean errorIfNoMatch,
Class extends Annotation>... targetTypes) {
return getAnnotationWrapper(method, errorIfNoMatch, Arrays.asList(targetTypes));
}
private Optional getAnnotationWrapper(Method method, boolean errorIfNoMatch,
Collection> targetTypes) {
String databaseId = configuration.getDatabaseId();
Map statementAnnotations = targetTypes.stream()
.flatMap(x -> Arrays.stream(method.getAnnotationsByType(x))).map(AnnotationWrapper::new)
.collect(Collectors.toMap(AnnotationWrapper::getDatabaseId, x -> x, (existing, duplicate) -> {
throw new BuilderException(
String.format("Detected conflicting annotations '%s' and '%s' on '%s'.", existing.getAnnotation(),
duplicate.getAnnotation(), method.getDeclaringClass().getName() + "." + method.getName()));
}));
AnnotationWrapper annotationWrapper = null;
if (databaseId != null) {
annotationWrapper = statementAnnotations.get(databaseId);
}
if (annotationWrapper == null) {
annotationWrapper = statementAnnotations.get("");
}
if (errorIfNoMatch && annotationWrapper == null && !statementAnnotations.isEmpty()) {
// Annotations exist, but there is no matching one for the specified databaseId
throw new BuilderException(String.format(
"Could not find a statement annotation that correspond a current database or default statement on method '%s.%s'. Current database id is [%s].",
method.getDeclaringClass().getName(), method.getName(), databaseId));
}
return Optional.ofNullable(annotationWrapper);
}
public static Class> getMethodReturnType(String mapperFqn, String localStatementId) {
if (mapperFqn == null || localStatementId == null) {
return null;
}
try {
Class> mapperClass = Resources.classForName(mapperFqn);
for (Method method : mapperClass.getMethods()) {
if (method.getName().equals(localStatementId) && canHaveStatement(method)) {
return getReturnType(method, mapperClass);
}
}
} catch (ClassNotFoundException e) {
// No corresponding mapper interface which is OK
}
return null;
}
private static class AnnotationWrapper {
private final Annotation annotation;
private final String databaseId;
private final SqlCommandType sqlCommandType;
private boolean dirtySelect;
AnnotationWrapper(Annotation annotation) {
this.annotation = annotation;
if (annotation instanceof Select) {
databaseId = ((Select) annotation).databaseId();
sqlCommandType = SqlCommandType.SELECT;
dirtySelect = ((Select) annotation).affectData();
} else if (annotation instanceof Update) {
databaseId = ((Update) annotation).databaseId();
sqlCommandType = SqlCommandType.UPDATE;
} else if (annotation instanceof Insert) {
databaseId = ((Insert) annotation).databaseId();
sqlCommandType = SqlCommandType.INSERT;
} else if (annotation instanceof Delete) {
databaseId = ((Delete) annotation).databaseId();
sqlCommandType = SqlCommandType.DELETE;
} else if (annotation instanceof SelectProvider) {
databaseId = ((SelectProvider) annotation).databaseId();
sqlCommandType = SqlCommandType.SELECT;
dirtySelect = ((SelectProvider) annotation).affectData();
} else if (annotation instanceof UpdateProvider) {
databaseId = ((UpdateProvider) annotation).databaseId();
sqlCommandType = SqlCommandType.UPDATE;
} else if (annotation instanceof InsertProvider) {
databaseId = ((InsertProvider) annotation).databaseId();
sqlCommandType = SqlCommandType.INSERT;
} else if (annotation instanceof DeleteProvider) {
databaseId = ((DeleteProvider) annotation).databaseId();
sqlCommandType = SqlCommandType.DELETE;
} else {
sqlCommandType = SqlCommandType.UNKNOWN;
if (annotation instanceof Options) {
databaseId = ((Options) annotation).databaseId();
} else if (annotation instanceof SelectKey) {
databaseId = ((SelectKey) annotation).databaseId();
} else {
databaseId = "";
}
}
}
Annotation getAnnotation() {
return annotation;
}
SqlCommandType getSqlCommandType() {
return sqlCommandType;
}
String getDatabaseId() {
return databaseId;
}
boolean isDirtySelect() {
return dirtySelect;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy