本文次要钻研一下如果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办法都是以最先呈现的为准。