mybatis-plus的sql加載順序源碼解析
序
本文主要研究一下如果mybatis mapper定義了多個(gè)同名方法會不會有問題
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>調(diào)整后的 SQL優(yōu)先級:XmlSql > sqlProvider > CurdSql </p> */ @Override public void addMappedStatement(MappedStatement ms) { if (mappedStatements.containsKey(ms.getId())) { /* * 說明已加載了xml中的節(jié)點(diǎn); 忽略mapper中的 SqlProvider 數(shù)據(jù) */ 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方法會根據(jù)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方法會執(zhí)行configurationElement,即加載xml的mapper方法,之后執(zhí)行bindMapperForNamespace,加載對應(yīng)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進(jìn)行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 動(dòng)態(tài) 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(); }
這里通過反射獲取對應(yīng)java mapper的方法(這里的順序是先接口本身定義的方法,然后是逐層繼承的接口定義的方法),然后挨個(gè)執(zhí)行parseStatement,接著執(zhí)行parserInjector來處理內(nèi)置的通過SqlMethod提供的內(nèi)置方法
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)) { // 循環(huán)注入自定義方法 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會拼接上當(dāng)前的namespace
小結(jié)
如果mybatis mapper定義了多個(gè)同名方法,則啟動(dòng)時(shí)不會報(bào)錯(cuò),但是會有error日志告知同名方法被忽略。整體加載順序是xml的方法優(yōu)先于java mapper定義的方法,優(yōu)先于自定義的SqlMethod;而xml或者java mapper方法都是以最先出現(xiàn)的為準(zhǔn)。
以上就是mybatis-plus的sql加載順序源碼解析的詳細(xì)內(nèi)容,更多關(guān)于mybatis-plus sql加載順序的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Apache POI將PPT轉(zhuǎn)換成圖片實(shí)例代碼
這篇文章主要介紹了Apache POI將PPT轉(zhuǎn)換成圖片實(shí)例代碼,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01Java實(shí)現(xiàn)文件監(jiān)控器FileMonitor的實(shí)例代碼
這篇文章主要介紹了Java實(shí)現(xiàn)文件監(jiān)控器FileMonitor的實(shí)例代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12java利用遞歸算法實(shí)現(xiàn)對文件夾的刪除功能
這篇文章主要介紹了java利用遞歸算法實(shí)現(xiàn)對文件夾的刪除功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09@ComponentScan在spring中無效的原因分析及解決方案
這篇文章主要介紹了@ComponentScan在spring中無效的原因分析及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11MybatisPlus結(jié)合groupby實(shí)現(xiàn)分組和sum求和的步驟
這篇文章主要介紹了MybatisPlus結(jié)合groupby實(shí)現(xiàn)分組和sum求和的步驟,這次使用的是LambdaQueryWrapper,使用QueryWrapper相對來說簡單點(diǎn)就不寫了,本文分步驟給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2023-12-12基于Spring@Autowired注解與自動(dòng)裝配詳談
下面小編就為大家?guī)硪黄赟pring@Autowired注解與自動(dòng)裝配詳談。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10Java中BufferedReader與Scanner讀入的區(qū)別詳解
這篇文章主要介紹了Java中BufferedReader與Scanner讀入的區(qū)別詳解,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10關(guān)于spring事務(wù)傳播行為非事務(wù)方式的理解
這篇文章主要介紹了對spring事務(wù)傳播行為非事務(wù)方式的全面理解,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11