序
本文次要钻研一下如果 mybatis mapper 定义了多个同名办法会不会有问题
MybatisConfiguration
com/baomidou/mybatisplus/core/MybatisConfiguration.java
/**
* MybatisPlus 加载 SQL 程序:* <p> 1、加载 XML 中的 SQL </p>
* <p> 2、加载 SqlProvider 中的 SQL </p>
* <p> 3、XmlSql 与 SqlProvider 不能蕴含雷同的 SQL </p>
* <p> 调整后的 SQL 优先级:XmlSql > sqlProvider > CurdSql </p>
*/
@Override
public void addMappedStatement(MappedStatement ms) {if (mappedStatements.containsKey(ms.getId())) {
/*
* 阐明已加载了 xml 中的节点;疏忽 mapper 中的 SqlProvider 数据
*/
logger.error("mapper[" + ms.getId() + "] is ignored, because it exists, maybe from xml file");
return;
}
mappedStatements.put(ms.getId(), ms);
}
MybatisSqlSessionFactoryBean
com/baomidou/mybatisplus/extension/spring/MybatisSqlSessionFactoryBean.java
/**
* Build a {@code SqlSessionFactory} instance.
* <p>
* The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
* {@code SqlSessionFactory} instance based on an Reader. Since 1.3.0, it can be specified a
* {@link Configuration} instance directly(without config file).
* </p>
*
* @return SqlSessionFactory
* @throws IOException if loading the config file failed
*/
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
//......
if (this.mapperLocations != null) {if (this.mapperLocations.length == 0) {LOGGER.warn(() -> "Property'mapperLocations'was specified but matching resources are not found.");
} else {for (Resource mapperLocation : this.mapperLocations) {if (mapperLocation == null) {continue;}
try {XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();} catch (Exception e) {throw new IOException("Failed to parse mapping resource:'" + mapperLocation + "'", e);
} finally {ErrorContext.instance().reset();}
LOGGER.debug(() -> "Parsed mapper file:'" + mapperLocation + "'");
}
}
}
}
MybatisSqlSessionFactoryBean 的 buildSqlSessionFactory 办法会依据 mapperLocations 的配置取加载 xml 配置,即加载 xml 的 mapper 信息
XMLMapperBuilder
org/apache/ibatis/builder/xml/XMLMapperBuilder.java
public void parse() {if (!configuration.isResourceLoaded(resource)) {configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();}
private void bindMapperForNamespace() {String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {// ignore, bound type is not required}
if (boundType != null && !configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
XMLMapperBuilder 的 parse 办法会执行 configurationElement,即加载 xml 的 mapper 办法,之后执行 bindMapperForNamespace,加载对应 java mapper 的办法
MybatisMapperRegistry
com/baomidou/mybatisplus/core/MybatisMapperRegistry.java
@Override
public <T> void addMapper(Class<T> type) {if (type.isInterface()) {if (hasMapper(type)) {
// TODO 如果之前注入 间接返回
return;
// TODO 这里就不抛异样了
// throw new BindingException("Type" + type + "is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// TODO 这里也换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// TODO 这里也换成 MybatisMapperAnnotationBuilder 而不是 MapperAnnotationBuilder
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {if (!loadCompleted) {knownMappers.remove(type);
}
}
}
}
MybatisMapperRegistry 通过 MybatisMapperAnnotationBuilder 进行 parse
MybatisMapperAnnotationBuilder
com/baomidou/mybatisplus/core/MybatisMapperAnnotationBuilder.java
public void parse() {String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {loadXmlResource();
configuration.addLoadedResource(resource);
String mapperName = type.getName();
assistant.setCurrentNamespace(mapperName);
parseCache();
parseCacheRef();
IgnoreStrategy ignoreStrategy = InterceptorIgnoreHelper.initSqlParserInfoCache(type);
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 {
// TODO 退出 注解过滤缓存
InterceptorIgnoreHelper.initSqlParserInfoCache(ignoreStrategy, mapperName, method);
parseStatement(method);
} catch (IncompleteElementException e) {
// TODO 应用 MybatisMethodResolver 而不是 MethodResolver
configuration.addIncompleteMethod(new MybatisMethodResolver(this, method));
}
}
// TODO 注入 CURD 动静 SQL , 放在在最初, because 可能会有人会用注解重写 sql
try {
// https://github.com/baomidou/mybatis-plus/issues/3038
if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {parserInjector();
}
} catch (IncompleteElementException e) {configuration.addIncompleteMethod(new InjectorResolver(this));
}
}
parsePendingMethods();}
这里通过反射获取对应 java mapper 的办法 (
这里的程序是先接口自身定义的办法,而后是逐层继承的接口定义的办法
),而后挨个执行 parseStatement,接着执行 parserInjector 来解决内置的通过 SqlMethod 提供的内置办法
parseStatement
private static final Set<Class<? extends Annotation>> statementAnnotationTypes = Stream
.of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, UpdateProvider.class,
InsertProvider.class, DeleteProvider.class)
.collect(Collectors.toSet());
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() + StringPool.DOT + 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();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
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(StringPool.COMMA, resultMapAnnotation.value());
} else {resultMapId = generateResultMapName(method);
}
}
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
statementAnnotation.getDatabaseId(),
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
});
}
parseStatement 这里解析带有 Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, UpdateProvider.class, InsertProvider.class, DeleteProvider.class 注解的办法,而后通过 assistant.addMappedStatement 注册到 configuration 的 mappedStatements 中,key 为 statementId(
type.getName() + StringPool.DOT + method.getName()
)
parserInjector
com/baomidou/mybatisplus/core/MybatisMapperAnnotationBuilder.java
void parserInjector() {GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}
com/baomidou/mybatisplus/core/injector/AbstractSqlInjector.java
@Override
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {Class<?> modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0);
if (modelClass != null) {String className = mapperClass.toString();
Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
if (!mapperRegistryCache.contains(className)) {TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo);
if (CollectionUtils.isNotEmpty(methodList)) {
// 循环注入自定义办法
methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
} else {logger.debug(mapperClass.toString() + ", No effective injection method was found.");
}
mapperRegistryCache.add(className);
}
}
}
com/baomidou/mybatisplus/core/injector/AbstractMethod.java
public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {this.configuration = builderAssistant.getConfiguration();
this.builderAssistant = builderAssistant;
this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
/* 注入自定义办法 */
injectMappedStatement(mapperClass, modelClass, tableInfo);
}
protected MappedStatement addInsertMappedStatement(Class<?> mapperClass, Class<?> parameterType, String id,
SqlSource sqlSource, KeyGenerator keyGenerator,
String keyProperty, String keyColumn) {
return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.INSERT, parameterType, null,
Integer.class, keyGenerator, keyProperty, keyColumn);
}
protected MappedStatement addMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource,
SqlCommandType sqlCommandType, Class<?> parameterType,
String resultMap, Class<?> resultType, KeyGenerator keyGenerator,
String keyProperty, String keyColumn) {String statementName = mapperClass.getName() + DOT + id;
if (hasMappedStatement(statementName)) {logger.warn(LEFT_SQ_BRACKET + statementName + "] Has been loaded by XML or SqlProvider or Mybatis's Annotation, so ignoring this injection for [" + getClass() + RIGHT_SQ_BRACKET);
return null;
}
/* 缓存逻辑解决 */
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
return builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType,
null, null, null, parameterType, resultMap, resultType,
null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn,
configuration.getDatabaseId(), languageDriver, null);
}
这里会通过 statementName(
mapperClass.getName() + DOT + id
)j 检测是否存在,如果不存在则增加
org/apache/ibatis/builder/MapperBuilderAssistant.java
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
public String applyCurrentNamespace(String base, boolean isReference) {if (base == null) {return null;}
if (isReference) {
// is it qualified with any namespace yet?
if (base.contains(".")) {return base;}
} else {
// is it qualified with this namespace yet?
if (base.startsWith(currentNamespace + ".")) {return base;}
if (base.contains(".")) {throw new BuilderException("Dots are not allowed in element names, please remove it from" + base);
}
}
return currentNamespace + "." + base;
}
增加的话,最初的 id 会拼接上以后的 namespace
小结
如果 mybatis mapper 定义了多个同名办法,则启动时不会报错,然而会有 error 日志告知同名办法被疏忽。整体加载程序是 xml 的办法优先于 java mapper 定义的办法,优先于自定义的 SqlMethod;而 xml 或者 java mapper 办法都是以最先呈现的为准。