关于mybatis-plus:聊聊mybatisplus的sql加载顺序

10次阅读

共计 13196 个字符,预计需要花费 33 分钟才能阅读完成。

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

正文完
 0