All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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