No MyBatis mapper was found in '[啟動類]' package. Please check your configuration


No MyBatis mapper was found in '[XXX]' package

友情提示:搜到這篇文章的,一般是急於解決這個問題的,看下常見原因排除后,可以忽略分析過程直接看解決方案,我自己出現這個問題的原因主要是 各個jar包版本差異的問題引起的

環境:SpringBoot2.2.5+MyBatisPlus3.3.1+PageHelper1.2.10

常見原因

看下報錯信息說的的是在哪個包下沒有找到 MyBatis mapper ,然后確定下該包下是否真的沒有,在確定有的情況下 檢查下應用入口類 XxxApplication.java 中有沒有加入@MapperScan(basePackages = {""})注解,若沒有,查看在 Mapper 接口上是否添加 @Mapper 注解 (2者必須選其一配置) ,最后看 配置文件中的 mapper-locations 映射的 xml 位置對不對

不是上面說的原因可以接着往下看

現象及初步分析

在 springboot 項目啟動時一直報這個警告,如下圖:

1589857467981

這個警告,一般對項目的運行沒有影響,但是強迫症患者不能忍受,因此簡單跟蹤下原因,根據警告日志跟蹤到日志打印的地方

org.mybatis.spring.mapper.ClassPathMapperScanner#doScan

1589858104417

可以看到這里是在掃描 啟動類目錄下 去找 mybatis 的Mapper,一般我們肯定不會在啟動類同級目錄下新建Mapper 接口,因此沒有找到,所以會給出沒有找到的警告

通用的解決方案

既然他說沒有,那我在在啟動類的同級目錄下新建一個 Mapper 供掃描,是不是就不警告了?

是的,這也是能百度到的最靠譜的方案了,添加如下接口到啟動類同級目錄后,警告即可消失

import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface NoWarnMapper { }

但是這樣首先沒有找到問題的根源,項目可能存在隱患,令人不安,上面的解決方案只是權益之計,騙過警告不讓打印。其次啟動類同級目錄莫名多了一個接口同樣令人不爽。因此可能還是需要進一步探究原因

深入探究

上面說了,之所以彈出警告,是因為掃描包找 Mapper 時沒有找到,排查掉常見原因后,就會產生疑惑,啟動類上配置了 mapper包的掃描路徑,為什么不去掃呢,接上面分析,接着來看到底為何會掃描啟動類同級目錄,初步分析的圖調用鏈往上走,看看是誰調用了 doScan 方法

image-20200521115946502

org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar

可以看到是 是 AutoConfiguredMapperScannerRegistrar 的 registerBeanDefinitions,這個類實現了 ImportBeanDefinitionRegistrar 接口,重寫了 registerBeanDefinitions 方法 ,在spring 體系中 ImportBeanDefinitionRegistrar 接口是 @Import 注解3種導入使用方式之一,接着向上找調用鏈

image-20200521171340863

找到 類:

org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.MapperScannerRegistrarNotFoundConfiguration

  @org.springframework.context.annotation.Configuration
  @Import({ AutoConfiguredMapperScannerRegistrar.class })
  @ConditionalOnMissingBean(MapperFactoryBean.class)
  public static class MapperScannerRegistrarNotFoundConfiguration {

    @PostConstruct
    public void afterPropertiesSet() {
      logger.debug("No {} found.", MapperFactoryBean.class.getName());
    }
  }

可以看到由該類 導入了 AutoConfiguredMapperScannerRegistrar 該類本身的生效條件由條件注解決定 即 @ConditionalOnMissingBean(MapperFactoryBean.class) ,這個條件的意思就是當容器中不存在 MapperFactoryBean 類型時生效,MapperFactoryBean 是我們 mybatis mapper 的工廠bean,一個mapper 對應一個 MapperFactoryBean。

那么為什么 容器內沒有 MapperFactoryBean 呢?我們明明配置了 @MapperScan?

其實原因比較簡單 我們最終會發現 No MyBatis mapper was found in '[XXX]' package 警告的彈出並不影響我們程序的正常運行,也就是說 @MapperScan 一定是生效的,只是時機不對,即 在注冊 MapperScannerRegistrarNotFoundConfiguration 時,@MapperScan並未得到解析,因此容器中並沒有 MapperFactoryBean,因此 MapperScannerRegistrarNotFoundConfiguration 會導入 AutoConfiguredMapperScannerRegistrar ,然后發生警告,下面細說下

@MapperScan 注解做了什么?點到注解里面去 @Import(MapperScannerRegistrar.class),導入的類又是一個 ImportBeanDefinitionRegistrar 的實現類,那么它往容器中注冊了什么?

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
    .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
    }
  }

  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
    // 這里希望 注冊  MapperScannerConfigurer
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    // .....省略
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }

從代碼中可以看到關鍵類 MapperScannerConfigurer 他實現了 BeanDefinitionRegistryPostProcessor 接口,該接口 繼承 BeanFactoryPostProcessor 接口,這種接口的解析在哪?在這里

org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.List<org.springframework.beans.factory.config.BeanFactoryPostProcessor>)

public static void invokeBeanFactoryPostProcessors(
			ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

		//第一步:首先調用BeanDefinitionRegistryPostProcessor的后置處理器
		Set<String> processedBeans = new HashSet<>();

		//判斷我們的beanFacotry實現了BeanDefinitionRegistry
		if (beanFactory instanceof BeanDefinitionRegistry) {
			//強行把我們的bean工廠轉為BeanDefinitionRegistry
			BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
			//保存BeanFactoryPostProcessor類型的后置
			List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
			//保存BeanDefinitionRegistryPostProcessor類型的后置處理器
			List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();

			//循環我們傳遞進來的beanFactoryPostProcessors
			for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
				//判斷我們的后置處理器是不是BeanDefinitionRegistryPostProcessor
				if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
					//進行強制轉化
					BeanDefinitionRegistryPostProcessor registryProcessor =
							(BeanDefinitionRegistryPostProcessor) postProcessor;
					//調用他作為BeanDefinitionRegistryPostProcessor的處理器的后置方法
					registryProcessor.postProcessBeanDefinitionRegistry(registry);
					//添加到我們用於保存的BeanDefinitionRegistryPostProcessor的集合中
					registryProcessors.add(registryProcessor);
				}
				else {//若沒有實現BeanDefinitionRegistryPostProcessor 接口,那么他就是BeanFactoryPostProcessor
					//把當前的后置處理器加入到regularPostProcessors中
					regularPostProcessors.add(postProcessor);
				}
			}

			//定義一個集合用戶保存當前准備創建的BeanDefinitionRegistryPostProcessor
			List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

			//第一步:去容器中獲取BeanDefinitionRegistryPostProcessor的bean的處理器名稱
			String[] postProcessorNames =
					beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
			//循環上一步獲取的BeanDefinitionRegistryPostProcessor的類型名稱
			for (String ppName : postProcessorNames) {
				//判斷是否實現了PriorityOrdered接口的
				if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
					//顯示的調用getBean()的方式獲取出該對象然后加入到currentRegistryProcessors集合中去
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					//同時也加入到processedBeans集合中去
					processedBeans.add(ppName);
				}
			}
			//對currentRegistryProcessors集合中BeanDefinitionRegistryPostProcessor進行排序
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			//把他加入到用於保存到registryProcessors中
			registryProcessors.addAll(currentRegistryProcessors);
			
			 // 在這里典型的BeanDefinitionRegistryPostProcessor就是ConfigurationClassPostProcessor
			 // 用於進行bean定義的加載 比如我們的包掃描,@import  等等。。。。。。。。。
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
			//調用完之后,馬上clea掉
			currentRegistryProcessors.clear();

			//去容器中獲取BeanDefinitionRegistryPostProcessor的bean的處理器名稱
			postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
			//循環上一步獲取的BeanDefinitionRegistryPostProcessor的類型名稱
			for (String ppName : postProcessorNames) {
				//表示沒有被處理過,且實現了Ordered接口的
				if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
					//顯示的調用getBean()的方式獲取出該對象然后加入到currentRegistryProcessors集合中去
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					//同時也加入到processedBeans集合中去
					processedBeans.add(ppName);
				}
			}
			//對currentRegistryProcessors集合中BeanDefinitionRegistryPostProcessor進行排序
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			//把他加入到用於保存到registryProcessors中
			registryProcessors.addAll(currentRegistryProcessors);
			//調用他的后置處理方法
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
			//調用完之后,馬上clea掉
			currentRegistryProcessors.clear();

			//調用沒有實現任何優先級接口的BeanDefinitionRegistryPostProcessor
			//定義一個重復處理的開關變量 默認值為true
			boolean reiterate = true;
			//第一次就可以進來
			while (reiterate) {
				//進入循環馬上把開關變量給改為fasle
				reiterate = false;
				//去容器中獲取BeanDefinitionRegistryPostProcessor的bean的處理器名稱
				postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
				//循環上一步獲取的BeanDefinitionRegistryPostProcessor的類型名稱
				for (String ppName : postProcessorNames) {
					//沒有被處理過的
					if (!processedBeans.contains(ppName)) {
						//顯示的調用getBean()的方式獲取出該對象然后加入到currentRegistryProcessors集合中去
						currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
						//同時也加入到processedBeans集合中去
						processedBeans.add(ppName);
						//再次設置為true
						reiterate = true;
					}
				}
				//對currentRegistryProcessors集合中BeanDefinitionRegistryPostProcessor進行排序
				sortPostProcessors(currentRegistryProcessors, beanFactory);
				//把他加入到用於保存到registryProcessors中
				registryProcessors.addAll(currentRegistryProcessors);
				//調用他的后置處理方法
				invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
				//進行clear
				currentRegistryProcessors.clear();
			}

			//調用實現了BeanDefinitionRegistryPostProcessor的接口 他是他也同時實現了BeanFactoryPostProcessor的方法
			invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
			//調用BeanFactoryPostProcessor成品的不是通過getBean的
			invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
		}

		else { //若當前的beanFacotory沒有實現了BeanDefinitionRegistry 直接電泳
			 //直接電泳beanFacotoryPostProcessor接口的方法進行后置處理
			invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
		}

		//獲取容器中所有的 BeanFactoryPostProcessor
		String[] postProcessorNames =
				beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

		//保存BeanFactoryPostProcessor類型實現了priorityOrdered
		List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
		//保存BeanFactoryPostProcessor類型實現了Ordered接口的
		List<String> orderedPostProcessorNames = new ArrayList<>();
		//保存BeanFactoryPostProcessor沒有實現任何優先級接口的
		List<String> nonOrderedPostProcessorNames = new ArrayList<>();
		for (String ppName : postProcessorNames) {
			//processedBeans包含的話,表示在上面處理BeanDefinitionRegistryPostProcessor的時候處理過了
			if (processedBeans.contains(ppName)) {
				// skip - already processed in first phase above
			}
			//判斷是否實現了PriorityOrdered
			else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
				priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
			}
			//判斷是否實現了Ordered
			else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
				orderedPostProcessorNames.add(ppName);
			}
			//沒有實現任何的優先級接口的
			else {
				nonOrderedPostProcessorNames.add(ppName);
			}
		}

		// 先調用BeanFactoryPostProcessor實現了 PriorityOrdered接口的
		sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
		invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);

		//再調用BeanFactoryPostProcessor實現了 Ordered.
		List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>();
		for (String postProcessorName : orderedPostProcessorNames) {
			orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
		}
		sortPostProcessors(orderedPostProcessors, beanFactory);
		invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);

		//調用沒有實現任何方法接口的
		List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>();
		for (String postProcessorName : nonOrderedPostProcessorNames) {
			nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
		}
		invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);

		// Clear cached merged bean definitions since the post-processors might have
		// modified the original metadata, e.g. replacing placeholders in values...
		beanFactory.clearMetadataCache();
	}

解析 BeanDefinitionRegistryPostProcessor 順序小結:

1)實現了PriorityOrdered接口的
2)實現了Ordered接口的
3)沒有實現任何的優先級接口的

我們的 MapperScannerConfigurer 就是第3類 沒有實現任何的優先級接口的,而 MapperScannerRegistrarNotFoundConfiguration 上的 @Configuration 對應的 BeanDefinitionRegistryPostProcessor 是 ConfigurationClassPostProcessor這個類是實現了 PriorityOrdered接口,因此先解析,放張源碼解析位置的對比圖

image-20200523175416743

問題分析到這,產生原因基本就清楚了:

spring 在 處理 MapperScannerRegistrarNotFoundConfiguration 這個配置類時,根據其類上的條件注解決定是否導入 AutoConfiguredMapperScannerRegistrar ,AutoConfiguredMapperScannerRegistrar 類 是 ImportBeanDefinitionRegistrar 接口的實現類,在調用 registerBeanDefinitions 時執行掃描,發現沒有 mapper 時,執行警告

為啥 MapperScannerRegistrarNotFoundConfiguration 生效,是因為解析 MapperScannerRegistrarNotFoundConfiguration 時 容器內確實沒有 MapperFactoryBean,沒有的原因是 我們配置的 @MapperScan 對應的 BeanDefinitionRegistryPostProcessor 在 @Configuration 對應的 BeanDefinitionRegistryPostProcessor 后面解析,因此相當於 解析 MapperScannerRegistrarNotFoundConfiguration 時,@MapperScan 還未生效

版本問題造成解決方案

這種解決方案就多了,就以我自身上面環境 SpringBoot2.2.5+MyBatisPlus3.3.1+PageHelper1.2.10 碰到的問題舉例,其他版本問題可參考解決

方案一 升級 PageHelper

解決方案

高版本的 PageHelper 使用的是高版本的 mybatis-spring-boot-starter 即高版本的MybatisPlusAutoConfiguration ,通過上面的分析應當是可以解決警告問題的

        <!--分頁插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.13</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis-spring</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

解決原理

低版本的 MybatisPlusAutoConfiguration

  @org.springframework.context.annotation.Configuration
  @Import({ AutoConfiguredMapperScannerRegistrar.class })
  @ConditionalOnMissingBean(MapperFactoryBean.class)
  public static class MapperScannerRegistrarNotFoundConfiguration {

    @PostConstruct
    public void afterPropertiesSet() {
      logger.debug("No {} found.", MapperFactoryBean.class.getName());
    }
  }

高版本的 MybatisPlusAutoConfiguration

  @org.springframework.context.annotation.Configuration
  @Import(AutoConfiguredMapperScannerRegistrar.class)
  @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
  public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
      logger.debug(
          "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
    }

  }

可以看到 高版本的 MybatisPlusAutoConfiguration 的內部類 MapperScannerRegistrarNotFoundConfiguration 配置的條件注解 多了一個 MapperScannerConfigurer.class

在 解析 MapperScannerRegistrarNotFoundConfiguration 的條件注解時,可以找到 MapperScannerConfigurer,因此 MapperScannerRegistrarNotFoundConfiguration 配置不會生效

org.springframework.boot.autoconfigure.condition.OnBeanCondition#getMatchOutcome

image-20200520183117248

方案二 排除 PageHelper 的傳遞依賴,改為我們手動控制

解決方案

排除 PageHelper 的傳遞依賴,改為我們手動控制

        <!--分頁插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.10</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis-spring</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.mybatis.spring.boot</groupId>
                    <artifactId>mybatis-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>

本身我是只打算排除就行的,我認為 mybatisplus應該已經有了依賴后來發現沒有。。。

解決原理

同方案一

方案三 降級 mybatis-spring 至 2.0.0版本

解決方案

降級 mybatis-spring 至 2.0.0版本

   <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.0</version>
    </dependency>

解決原理

MapperScannerRegistrarNotFoundConfiguration 的條件注解為 @ConditionalOnMissingBean(MapperFactoryBean.class),使條件注解不成立即可

  @org.springframework.context.annotation.Configuration
  @Import({ AutoConfiguredMapperScannerRegistrar.class })
  @ConditionalOnMissingBean(MapperFactoryBean.class)
  public static class MapperScannerRegistrarNotFoundConfiguration {

    @PostConstruct
    public void afterPropertiesSet() {
      logger.debug("No {} found.", MapperFactoryBean.class.getName());
    }
  }

mybatis-spring 降級至 2.0.0版本后,在啟動時,spring 在解析時,啟動類是優先於 MapperScannerRegistrarNotFoundConfiguration

org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions

	/**
	 * Read {@code configurationModel}, registering bean definitions
	 * with the registry based on its contents.
	 */
	public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
		for (ConfigurationClass configClass : configurationModel) {
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}

configurationModel 集合內容如下:

1589965413408

處理啟動類調用棧如下

doScan:155, ClassPathMapperScanner (org.mybatis.spring.mapper)
registerBeanDefinitions:123, MapperScannerRegistrar (org.mybatis.spring.annotation)
registerBeanDefinitions:72, MapperScannerRegistrar (org.mybatis.spring.annotation)
registerBeanDefinitions:86, ImportBeanDefinitionRegistrar (org.springframework.context.annotation)
lambda$loadBeanDefinitionsFromRegistrars$1:385, ConfigurationClassBeanDefinitionReader (org.springframework.context.annotation)
accept:-1, 2114701475 (org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$$Lambda$243)
forEach:684, LinkedHashMap (java.util)
loadBeanDefinitionsFromRegistrars:384, ConfigurationClassBeanDefinitionReader (org.springframework.context.annotation)
loadBeanDefinitionsForConfigurationClass:148, ConfigurationClassBeanDefinitionReader (org.springframework.context.annotation)


loadBeanDefinitions:120, ConfigurationClassBeanDefinitionReader (org.springframework.context.annotation)
processConfigBeanDefinitions:331, ConfigurationClassPostProcessor (org.springframework.context.annotation)
postProcessBeanDefinitionRegistry:236, ConfigurationClassPostProcessor (org.springframework.context.annotation)
invokeBeanDefinitionRegistryPostProcessors:275, PostProcessorRegistrationDelegate (org.springframework.context.support)
invokeBeanFactoryPostProcessors:95, PostProcessorRegistrationDelegate (org.springframework.context.support)
invokeBeanFactoryPostProcessors:706, AbstractApplicationContext (org.springframework.context.support)
refresh:532, AbstractApplicationContext (org.springframework.context.support)
refresh:141, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:747, SpringApplication (org.springframework.boot)
refreshContext:397, SpringApplication (org.springframework.boot)
run:315, SpringApplication (org.springframework.boot)
run:1226, SpringApplication (org.springframework.boot)
run:1215, SpringApplication (org.springframework.boot)
main:26, UhuApplication (com.fungo.youhu)

啟動類解析掃描后(調用完 doScan),容器中就存在 MapperFactoryBean 了,此時解析 MapperScannerRegistrarNotFoundConfiguration 條件注解時,條件就不成立了

org.springframework.boot.autoconfigure.condition.OnBeanCondition#getMatchOutcome

1589940789024

那么為什么要降版本呢?因為 2.0.0 版本會調用 doScan() 方法

org.mybatis.spring.annotation.MapperScannerRegistrar#registerBeanDefinitions(org.springframework.core.annotation.AnnotationAttributes, org.springframework.beans.factory.support.BeanDefinitionRegistry)

高版本 的 MapperScannerRegistrar#registerBeanDefinitions 方法內就不會在調用 doScan() 了

方案四 自己整合 PageHelper 至springboot

解決原理

如上分析可知 根源其實就在於引入 pagehelper-spring-boot-starter 后,順帶引入了 mybatis-spring-boot-starter,由此帶來了一系列的版本問題,但是我這邊使用 pageHelper 只是單純使用他的基礎分頁功能,我覺得應該並不需要 mybatis-spring-boot-starter ,另一方面 mybatis-plus 也並未引入 mybatis-spring-boot-starter ,我覺得不應引入 mybatis-spring-boot-starter,因此參考原來的 pagehelper-spring-boot-starter 整合,將原始的 pageHelper 整合到 springboot 中

ps : 我並未閱讀過 pageHelper 源碼,只是自己整合后,分頁功能不受影響,已滿足我的需求,如需高級功能請自行測試使用哦

解決方案

  1. 引入 pageHelper 分頁插件 注意這里引入的不是 pagehelper-spring-boot-starter 而是原始的 pageHelper
        <!-- 引入pageHelper分頁插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.1.11</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
  1. 參考 pagehelper-spring-boot-starter,將 pagehelper 整合到 springboot

    2.1 pagehelper 參數配置類

    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    import java.util.Properties;
    
    /**
     * Configuration properties for PageHelper.
     */
    @ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX)
    public class PageHelperProperties {
    
        static final String PAGEHELPER_PREFIX = "pagehelper";
    
        private Properties properties = new Properties();
    
        public Properties getProperties() {
            return properties;
        }
    
        public String getOffsetAsPageNum() {
            return properties.getProperty("offsetAsPageNum");
        }
    
        public void setOffsetAsPageNum(String offsetAsPageNum) {
            properties.setProperty("offsetAsPageNum", offsetAsPageNum);
        }
    
        public String getRowBoundsWithCount() {
            return properties.getProperty("rowBoundsWithCount");
        }
    
        public void setRowBoundsWithCount(String rowBoundsWithCount) {
            properties.setProperty("rowBoundsWithCount", rowBoundsWithCount);
        }
    
        public String getPageSizeZero() {
            return properties.getProperty("pageSizeZero");
        }
    
        public void setPageSizeZero(String pageSizeZero) {
            properties.setProperty("pageSizeZero", pageSizeZero);
        }
    
        public String getReasonable() {
            return properties.getProperty("reasonable");
        }
    
        public void setReasonable(String reasonable) {
            properties.setProperty("reasonable", reasonable);
        }
    
        public String getSupportMethodsArguments() {
            return properties.getProperty("supportMethodsArguments");
        }
    
        public void setSupportMethodsArguments(String supportMethodsArguments) {
            properties.setProperty("supportMethodsArguments", supportMethodsArguments);
        }
    
        public String getDialect() {
            return properties.getProperty("dialect");
        }
    
        public void setDialect(String dialect) {
            properties.setProperty("dialect", dialect);
        }
    
        public String getHelperDialect() {
            return properties.getProperty("helperDialect");
        }
    
        public void setHelperDialect(String helperDialect) {
            properties.setProperty("helperDialect", helperDialect);
        }
    
        public String getAutoRuntimeDialect() {
            return properties.getProperty("autoRuntimeDialect");
        }
    
        public void setAutoRuntimeDialect(String autoRuntimeDialect) {
            properties.setProperty("autoRuntimeDialect", autoRuntimeDialect);
        }
    
        public String getAutoDialect() {
            return properties.getProperty("autoDialect");
        }
    
        public void setAutoDialect(String autoDialect) {
            properties.setProperty("autoDialect", autoDialect);
        }
    
        public String getCloseConn() {
            return properties.getProperty("closeConn");
        }
    
        public void setCloseConn(String closeConn) {
            properties.setProperty("closeConn", closeConn);
        }
    
        public String getParams() {
            return properties.getProperty("params");
        }
    
        public void setParams(String params) {
            properties.setProperty("params", params);
        }
    
        public String getDefaultCount() {
            return properties.getProperty("defaultCount");
        }
    
        public void setDefaultCount(String defaultCount) {
            properties.setProperty("defaultCount", defaultCount);
        }
    
        public String getDialectAlias() {
            return properties.getProperty("dialectAlias");
        }
    
        public void setDialectAlias(String dialectAlias) {
            properties.setProperty("dialectAlias", dialectAlias);
        }
    }
    

    2.2 自動裝配類

    /**
     *  PageHelper 分頁插件配置
     */
    @Configuration
    @EnableConfigurationProperties(PageHelperProperties.class)
    public class PageHelperAutoConfiguration {
    
        @Resource
        private PageHelperProperties properties;
    
        /**
         * 接受分頁插件額外的屬性
         */
        @Bean
        @ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX)
        public Properties pageHelperProperties() {
            return new Properties();
        }
    
    
        @Bean
        public PageInterceptor pageInterceptor() {
            PageInterceptor interceptor = new PageInterceptor();
            Properties properties = new Properties();
            //先把一般方式配置的屬性放進去
            properties.putAll(pageHelperProperties());
            //在把特殊配置放進去,由於close-conn 利用上面方式時,屬性名就是 close-conn 而不是 closeConn,所以需要額外的一步
            properties.putAll(this.properties.getProperties());
            interceptor.setProperties(properties);
            return interceptor;
        }
    

    2.3 yml 配置

    # 分頁配置
    pagehelper:
      # 指定使用的數據庫數據庫
      helperDialect: mysql
      # reasonable:分頁合理化參數
      reasonable: true
      # 自動分頁 當查詢條件有 pageNum,pageSize 參數時,自動執行分頁
      support-methods-arguments: false
    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM