com.github.nomou.mybatis.builder.AutoDetectMapperBuilder Maven / Gradle / Ivy
package com.github.nomou.mybatis.builder;
import com.github.nomou.mybatis.NoSmart;
import com.github.nomou.mybatis.SmartDefinition;
import com.github.nomou.mybatis.builder.spi.SqlSourceCreator;
import com.github.nomou.mybatis.builder.spi.SqlSourceCreators;
import freework.reflect.Types;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.CacheNamespaceRef;
import org.apache.ibatis.annotations.ConstructorArgs;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.SelectKey;
import org.apache.ibatis.annotations.TypeDiscriminator;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
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.MappedStatement;
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.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 java.io.IOException;
import java.io.InputStream;
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.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* 当前类用于按照规则为没有实现的mapper方法生成实现.
*
* 以下情况不会生成:
*
* - xml中已经配置了对应的 statement
* - 使用annotation标注了的method
*
*
*
* @author vacoor
* @see XMLMapperBuilder
* @see MapperAnnotationBuilder
*/
public class AutoDetectMapperBuilder extends AnnotatedMapperSupport {
/**
* 资源类型, 用于标识当前Builder生成的 Mapper.
*/
private static final String NS_TYPE = "smart";
/**
* Mapper接口.
*/
private final Class> type;
/**
* Mybatis配置对象.
*/
private final Configuration configuration;
/**
* Mapper构建助手.
*/
private final MapperBuilderAssistant assistant;
/**
* 使用给定的mapper和配置创建{@link AutoDetectMapperBuilder} 实例.
*
* @param type mapper接口
* @param configuration mybatis配置
*/
private AutoDetectMapperBuilder(final Class> type, final Configuration configuration) {
super(new MapperBuilderAssistant(configuration, type.getName().replace('.', '/') + ".java (" + NS_TYPE + ')'));
this.type = type;
this.configuration = configuration;
this.assistant = super.assistant;
}
/**
* 执行解析操作.
*
* @see MapperAnnotationBuilder#parse()
*/
public void parse() {
final String resource = NS_TYPE + ":" + type.getName();
if (!configuration.isResourceLoaded(resource)) {
this.loadXmlResourceIfNecessary();
this.loadAnnotationResourceIfNecessary();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
this.useCache(type.getAnnotation(CacheNamespace.class));
this.useCacheRef(type.getAnnotation(CacheNamespaceRef.class));
final Map alreadyCreatedStatements = new HashMap();
for (final Method method : type.getMethods()) {
// FIXED subinterface implementation generic interface
if (!method.isBridge() && !isInterfaceBridge(method, type)) {
/*-
* 如果创建失败是因为其他方式已经创建, 则直接跳过.
* 如果创建是因为当前接口定义了相同名称的方法则报错.
*/
final String statementId = type.getName() + "." + method.getName();
if (this.createSmartStatementIfNecessary(method, statementId)) {
alreadyCreatedStatements.put(statementId, method);
} else if (alreadyCreatedStatements.containsKey(statementId)) {
final Method ownerMethod = alreadyCreatedStatements.get(statementId);
throw new IllegalStateException(String.format("duplicated method statement id '%s' found: %s", statementId, Arrays.toString(new Method[]{method, ownerMethod})));
}
}
}
}
}
// https://stackoverflow.com/questions/12373607/reflection-on-interface-overridden-methods
private boolean isInterfaceBridge(final Method method, final Class> runtimeClass) {
boolean bridge = false;
if (!method.getDeclaringClass().equals(runtimeClass)) {
final Type[] parameterTypes = method.getGenericParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
parameterTypes[i] = Types.resolveType(parameterTypes[i], runtimeClass);
}
for (final Method candidate : runtimeClass.getMethods()) {
if (candidate.equals(method)) {
continue;
}
if (candidate.getName().equals(method.getName()) && Arrays.equals(candidate.getParameterTypes(), parameterTypes)) {
System.err.println("Skip interface bridge:" + method);
bridge = true;
break;
}
}
}
return bridge;
}
/**
* @param method
* @see MapperAnnotationBuilder#parseStatement(Method)
*/
private boolean createSmartStatementIfNecessary(final Method method, final String statementId) {
if (configuration.hasStatement(statementId, true)) {
return false;
}
final LanguageDriver languageDriver = assistant.getLanguageDriver(null);
final Class> parameterType = getParameterType(method);
final Class> returnType = getReturnType(type, method);
SqlSourceCreator> sqlSourceCreator;
try {
sqlSourceCreator = SqlSourceCreators.create(type, method, languageDriver, configuration);
} catch (final Exception ex) {
throw new IllegalStateException(String.format("instantiate SqlSourceCreator implementation happen error at %s", statementId), ex);
}
Connection conn = null;
try {
conn = configuration.getEnvironment().getDataSource().getConnection();
final DatabaseMetaData metadata = conn.getMetaData();
System.out.println(metadata.getDatabaseProductName());
// TODO OffsetNLimit 支持
} catch (final SQLException e) {
throw new IllegalStateException("cannot get database metadata");
} finally {
if (null != conn) {
try {
conn.close();
} catch (SQLException ignore) {
}
}
}
final SqlCommandType sqlCommandType = sqlSourceCreator.getSqlCommandType();
final SqlSource sqlSource = sqlSourceCreator.createSqlSource(parameterType);
if (null != sqlSource) {
// 处理配置.
final Options options = method.getAnnotation(Options.class);
final Integer fetchSize = determineFetchSize(options);
final Integer timeout = null != options && -1 < options.timeout() ? options.timeout() : null;
final StatementType statementType = null != options ? options.statementType() : StatementType.PREPARED;
final ResultSetType resultSetType = null != options ? options.resultSetType() : ResultSetType.FORWARD_ONLY;
final boolean isSelect = SqlCommandType.SELECT.equals(sqlCommandType);
final boolean flushCache = determineFlushCache(options, !isSelect);
final boolean useCache = null != options ? options.useCache() : isSelect;
// 处理主键生成.
KeyGenerator keyGenerator;
String keyProperty = "id";
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
final SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (null != selectKey) {
keyGenerator = handleSelectKeyAnnotation(selectKey, statementId, parameterType, languageDriver);
keyProperty = selectKey.keyProperty();
} else if (null != options) {
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
} else {
keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
// 处理ResultMap
String resultMapId = null;
final ResultMap resultMap = method.getAnnotation(ResultMap.class);
if (null != resultMap) {
// 如果显式指定了ResultMap, 使用给定ResultMap
final String[] resultMaps = resultMap.value();
final StringBuilder buff = new StringBuilder();
for (final String value : resultMaps) {
if (0 < buff.length()) {
buff.append(',');
}
buff.append(value);
}
resultMapId = buff.toString();
} else if (isSelect) {
// 没有指定, 但是是查询.
resultMapId = parseResultMap(method);
}
assistant.addMappedStatement(
statementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterType,
resultMapId,
returnType,
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null
);
}
return true;
}
private Integer determineFetchSize(final Options options) {
final int fetchSize = null != options ? options.fetchSize() : -1;
return -1 < fetchSize || Integer.MIN_VALUE == fetchSize ? fetchSize : null;
}
private boolean determineFlushCache(final Options options, final boolean def) {
if (null != options) {
final Options.FlushCachePolicy policy = options.flushCache();
if (Options.FlushCachePolicy.TRUE.equals(policy)) {
return true;
}
if (Options.FlushCachePolicy.FALSE.equals(policy)) {
return false;
}
}
return def;
}
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;
SqlSource sqlSource = buildSqlSourceFromStrings(selectKeyAnnotation.statement(), parameterTypeClass, languageDriver);
SqlCommandType sqlCommandType = SqlCommandType.SELECT;
assistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum,
flushCache, useCache, false,
keyGenerator, keyProperty, keyColumn, null, languageDriver, null);
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 buildSqlSourceFromStrings(String[] strings, Class> parameterTypeClass, LanguageDriver languageDriver) {
final StringBuilder sql = new StringBuilder();
for (String fragment : strings) {
sql.append(fragment);
sql.append(" ");
}
return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
}
private String nullOrEmpty(String value) {
return value == null || value.trim().length() == 0 ? null : value;
}
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 = MapperMethod.ParamMap.class;
}
}
}
return parameterType;
}
private Class> getReturnType(Class> type, Method method) {
Class> returnType = method.getReturnType();
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, type);
if (resolvedReturnType instanceof Class) {
returnType = (Class>) resolvedReturnType;
if (returnType.isArray()) {
returnType = returnType.getComponentType();
}
} 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();
}
}
}
}
return returnType;
}
/**
* 如果没有加载XML资源, 则加载.
*/
private void loadXmlResourceIfNecessary() {
// 检查XML是否加载, 这个标识在'XMLMapperBuilder#bindMapperForNamespace'中设置.
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
final String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream in = null;
try {
in = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (final IOException e) {
// ignore, resource is not required
}
if (in != null) {
new XMLMapperBuilder(in, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()).parse();
}
}
}
/**
* 如果没有加载Annotation配置, 则加载.
*/
private void loadAnnotationResourceIfNecessary() {
// 检查annotation配置是否加载, 这个标识在'MapperAnnotationBuilder#parse'中设置.
final String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
new MapperAnnotationBuilder(configuration, type).parse();
}
}
private String parseResultMap(final Method method) {
Class> returnType = getReturnType(type, method);
final Results results = method.getAnnotation(Results.class);
final ConstructorArgs args = method.getAnnotation(ConstructorArgs.class);
final TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
final String resultMapId = generateResultMapName(method);
if (true) {
return createResultMap(resultMapId, returnType, results, args, 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;
}
public static void parse(final Class> type, final Configuration config) {
if (!type.isAnnotationPresent(NoSmart.class)) {
new AutoDetectMapperBuilder(type, config).parse();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy