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 {
<span class="hljs-meta">@PostConstruct</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">afterPropertiesSet</span><span class="hljs-params">()</span> {
  logger.debug(<span class="hljs-string">"No {} found."</span>, 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) {
	<span class="hljs-comment">//第一步:首先调用BeanDefinitionRegistryPostProcessor的后置处理器</span>
	Set&lt;String&gt; processedBeans = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashSet</span>&lt;&gt;();

	<span class="hljs-comment">//判断我们的beanFacotry实现了BeanDefinitionRegistry</span>
	<span class="hljs-keyword">if</span> (beanFactory <span class="hljs-keyword">instanceof</span> BeanDefinitionRegistry) {
		<span class="hljs-comment">//强行把我们的bean工厂转为BeanDefinitionRegistry</span>
		<span class="hljs-type">BeanDefinitionRegistry</span> <span class="hljs-variable">registry</span> <span class="hljs-operator">=</span> (BeanDefinitionRegistry) beanFactory;
		<span class="hljs-comment">//保存BeanFactoryPostProcessor类型的后置</span>
		List&lt;BeanFactoryPostProcessor&gt; regularPostProcessors = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();
		<span class="hljs-comment">//保存BeanDefinitionRegistryPostProcessor类型的后置处理器</span>
		List&lt;BeanDefinitionRegistryPostProcessor&gt; registryProcessors = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();

		<span class="hljs-comment">//循环我们传递进来的beanFactoryPostProcessors</span>
		<span class="hljs-keyword">for</span> (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
			<span class="hljs-comment">//判断我们的后置处理器是不是BeanDefinitionRegistryPostProcessor</span>
			<span class="hljs-keyword">if</span> (postProcessor <span class="hljs-keyword">instanceof</span> BeanDefinitionRegistryPostProcessor) {
				<span class="hljs-comment">//进行强制转化</span>
				<span class="hljs-type">BeanDefinitionRegistryPostProcessor</span> <span class="hljs-variable">registryProcessor</span> <span class="hljs-operator">=</span>
						(BeanDefinitionRegistryPostProcessor) postProcessor;
				<span class="hljs-comment">//调用他作为BeanDefinitionRegistryPostProcessor的处理器的后置方法</span>
				registryProcessor.postProcessBeanDefinitionRegistry(registry);
				<span class="hljs-comment">//添加到我们用于保存的BeanDefinitionRegistryPostProcessor的集合中</span>
				registryProcessors.add(registryProcessor);
			}
			<span class="hljs-keyword">else</span> {<span class="hljs-comment">//若没有实现BeanDefinitionRegistryPostProcessor 接口,那么他就是BeanFactoryPostProcessor</span>
				<span class="hljs-comment">//把当前的后置处理器加入到regularPostProcessors中</span>
				regularPostProcessors.add(postProcessor);
			}
		}

		<span class="hljs-comment">//定义一个集合用户保存当前准备创建的BeanDefinitionRegistryPostProcessor</span>
		List&lt;BeanDefinitionRegistryPostProcessor&gt; currentRegistryProcessors = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();

		<span class="hljs-comment">//第一步:去容器中获取BeanDefinitionRegistryPostProcessor的bean的处理器名称</span>
		String[] postProcessorNames =
				beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, <span class="hljs-literal">true</span>, <span class="hljs-literal">false</span>);
		<span class="hljs-comment">//循环上一步获取的BeanDefinitionRegistryPostProcessor的类型名称</span>
		<span class="hljs-keyword">for</span> (String ppName : postProcessorNames) {
			<span class="hljs-comment">//判断是否实现了PriorityOrdered接口的</span>
			<span class="hljs-keyword">if</span> (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
				<span class="hljs-comment">//显示的调用getBean()的方式获取出该对象然后加入到currentRegistryProcessors集合中去</span>
				currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
				<span class="hljs-comment">//同时也加入到processedBeans集合中去</span>
				processedBeans.add(ppName);
			}
		}
		<span class="hljs-comment">//对currentRegistryProcessors集合中BeanDefinitionRegistryPostProcessor进行排序</span>
		sortPostProcessors(currentRegistryProcessors, beanFactory);
		<span class="hljs-comment">//把他加入到用于保存到registryProcessors中</span>
		registryProcessors.addAll(currentRegistryProcessors);
		
		 <span class="hljs-comment">// 在这里典型的BeanDefinitionRegistryPostProcessor就是ConfigurationClassPostProcessor</span>
		 <span class="hljs-comment">// 用于进行bean定义的加载 比如我们的包扫描,@import  等等。。。。。。。。。</span>
		invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
		<span class="hljs-comment">//调用完之后,马上clea掉</span>
		currentRegistryProcessors.clear();

		<span class="hljs-comment">//去容器中获取BeanDefinitionRegistryPostProcessor的bean的处理器名称</span>
		postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, <span class="hljs-literal">true</span>, <span class="hljs-literal">false</span>);
		<span class="hljs-comment">//循环上一步获取的BeanDefinitionRegistryPostProcessor的类型名称</span>
		<span class="hljs-keyword">for</span> (String ppName : postProcessorNames) {
			<span class="hljs-comment">//表示没有被处理过,且实现了Ordered接口的</span>
			<span class="hljs-keyword">if</span> (!processedBeans.contains(ppName) &amp;&amp; beanFactory.isTypeMatch(ppName, Ordered.class)) {
				<span class="hljs-comment">//显示的调用getBean()的方式获取出该对象然后加入到currentRegistryProcessors集合中去</span>
				currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
				<span class="hljs-comment">//同时也加入到processedBeans集合中去</span>
				processedBeans.add(ppName);
			}
		}
		<span class="hljs-comment">//对currentRegistryProcessors集合中BeanDefinitionRegistryPostProcessor进行排序</span>
		sortPostProcessors(currentRegistryProcessors, beanFactory);
		<span class="hljs-comment">//把他加入到用于保存到registryProcessors中</span>
		registryProcessors.addAll(currentRegistryProcessors);
		<span class="hljs-comment">//调用他的后置处理方法</span>
		invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
		<span class="hljs-comment">//调用完之后,马上clea掉</span>
		currentRegistryProcessors.clear();

		<span class="hljs-comment">//调用没有实现任何优先级接口的BeanDefinitionRegistryPostProcessor</span>
		<span class="hljs-comment">//定义一个重复处理的开关变量 默认值为true</span>
		<span class="hljs-type">boolean</span> <span class="hljs-variable">reiterate</span> <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;
		<span class="hljs-comment">//第一次就可以进来</span>
		<span class="hljs-keyword">while</span> (reiterate) {
			<span class="hljs-comment">//进入循环马上把开关变量给改为fasle</span>
			reiterate = <span class="hljs-literal">false</span>;
			<span class="hljs-comment">//去容器中获取BeanDefinitionRegistryPostProcessor的bean的处理器名称</span>
			postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, <span class="hljs-literal">true</span>, <span class="hljs-literal">false</span>);
			<span class="hljs-comment">//循环上一步获取的BeanDefinitionRegistryPostProcessor的类型名称</span>
			<span class="hljs-keyword">for</span> (String ppName : postProcessorNames) {
				<span class="hljs-comment">//没有被处理过的</span>
				<span class="hljs-keyword">if</span> (!processedBeans.contains(ppName)) {
					<span class="hljs-comment">//显示的调用getBean()的方式获取出该对象然后加入到currentRegistryProcessors集合中去</span>
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					<span class="hljs-comment">//同时也加入到processedBeans集合中去</span>
					processedBeans.add(ppName);
					<span class="hljs-comment">//再次设置为true</span>
					reiterate = <span class="hljs-literal">true</span>;
				}
			}
			<span class="hljs-comment">//对currentRegistryProcessors集合中BeanDefinitionRegistryPostProcessor进行排序</span>
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			<span class="hljs-comment">//把他加入到用于保存到registryProcessors中</span>
			registryProcessors.addAll(currentRegistryProcessors);
			<span class="hljs-comment">//调用他的后置处理方法</span>
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
			<span class="hljs-comment">//进行clear</span>
			currentRegistryProcessors.clear();
		}

		<span class="hljs-comment">//调用实现了BeanDefinitionRegistryPostProcessor的接口 他是他也同时实现了BeanFactoryPostProcessor的方法</span>
		invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
		<span class="hljs-comment">//调用BeanFactoryPostProcessor成品的不是通过getBean的</span>
		invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
	}

	<span class="hljs-keyword">else</span> { <span class="hljs-comment">//若当前的beanFacotory没有实现了BeanDefinitionRegistry 直接电泳</span>
		 <span class="hljs-comment">//直接电泳beanFacotoryPostProcessor接口的方法进行后置处理</span>
		invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
	}

	<span class="hljs-comment">//获取容器中所有的 BeanFactoryPostProcessor</span>
	String[] postProcessorNames =
			beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, <span class="hljs-literal">true</span>, <span class="hljs-literal">false</span>);

	<span class="hljs-comment">//保存BeanFactoryPostProcessor类型实现了priorityOrdered</span>
	List&lt;BeanFactoryPostProcessor&gt; priorityOrderedPostProcessors = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();
	<span class="hljs-comment">//保存BeanFactoryPostProcessor类型实现了Ordered接口的</span>
	List&lt;String&gt; orderedPostProcessorNames = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();
	<span class="hljs-comment">//保存BeanFactoryPostProcessor没有实现任何优先级接口的</span>
	List&lt;String&gt; nonOrderedPostProcessorNames = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();
	<span class="hljs-keyword">for</span> (String ppName : postProcessorNames) {
		<span class="hljs-comment">//processedBeans包含的话,表示在上面处理BeanDefinitionRegistryPostProcessor的时候处理过了</span>
		<span class="hljs-keyword">if</span> (processedBeans.contains(ppName)) {
			<span class="hljs-comment">// skip - already processed in first phase above</span>
		}
		<span class="hljs-comment">//判断是否实现了PriorityOrdered</span>
		<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
			priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
		}
		<span class="hljs-comment">//判断是否实现了Ordered</span>
		<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (beanFactory.isTypeMatch(ppName, Ordered.class)) {
			orderedPostProcessorNames.add(ppName);
		}
		<span class="hljs-comment">//没有实现任何的优先级接口的</span>
		<span class="hljs-keyword">else</span> {
			nonOrderedPostProcessorNames.add(ppName);
		}
	}

	<span class="hljs-comment">// 先调用BeanFactoryPostProcessor实现了 PriorityOrdered接口的</span>
	sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
	invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);

	<span class="hljs-comment">//再调用BeanFactoryPostProcessor实现了 Ordered.</span>
	List&lt;BeanFactoryPostProcessor&gt; orderedPostProcessors = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();
	<span class="hljs-keyword">for</span> (String postProcessorName : orderedPostProcessorNames) {
		orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
	}
	sortPostProcessors(orderedPostProcessors, beanFactory);
	invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);

	<span class="hljs-comment">//调用没有实现任何方法接口的</span>
	List&lt;BeanFactoryPostProcessor&gt; nonOrderedPostProcessors = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();
	<span class="hljs-keyword">for</span> (String postProcessorName : nonOrderedPostProcessorNames) {
		nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
	}
	invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);

	<span class="hljs-comment">// Clear cached merged bean definitions since the post-processors might have</span>
	<span class="hljs-comment">// modified the original metadata, e.g. replacing placeholders in values...</span>
	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 {
<span class="hljs-meta">@PostConstruct</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">afterPropertiesSet</span><span class="hljs-params">()</span> {
  logger.debug(<span class="hljs-string">"No {} found."</span>, MapperFactoryBean.class.getName());
}

}

高版本的 MybatisPlusAutoConfiguration

  @org.springframework.context.annotation.Configuration
  @Import(AutoConfiguredMapperScannerRegistrar.class)
  @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
  public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">afterPropertiesSet</span><span class="hljs-params">()</span> {
  logger.debug(
      <span class="hljs-string">"Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer."</span>);
}

}

可以看到 高版本的 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 {
<span class="hljs-meta">@PostConstruct</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">afterPropertiesSet</span><span class="hljs-params">()</span> {
  logger.debug(<span class="hljs-string">"No {} found."</span>, 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.2 自动装配类

    /**
     *  PageHelper 分页插件配置
     */
    @Configuration
    @EnableConfigurationProperties(PageHelperProperties.class)
    public class PageHelperAutoConfiguration {
    
    <span class="hljs-meta">@Resource</span>
    <span class="hljs-keyword">private</span> PageHelperProperties properties;
    
    <span class="hljs-comment">/**
     * 接受分页插件额外的属性
     */</span>
    <span class="hljs-meta">@Bean</span>
    <span class="hljs-meta">@ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX)</span>
    <span class="hljs-keyword">public</span> Properties <span class="hljs-title function_">pageHelperProperties</span><span class="hljs-params">()</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Properties</span>();
    }
    
    
    <span class="hljs-meta">@Bean</span>
    <span class="hljs-keyword">public</span> PageInterceptor <span class="hljs-title function_">pageInterceptor</span><span class="hljs-params">()</span> {
        <span class="hljs-type">PageInterceptor</span> <span class="hljs-variable">interceptor</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">PageInterceptor</span>();
        <span class="hljs-type">Properties</span> <span class="hljs-variable">properties</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Properties</span>();
        <span class="hljs-comment">//先把一般方式配置的属性放进去</span>
        properties.putAll(pageHelperProperties());
        <span class="hljs-comment">//在把特殊配置放进去,由于close-conn 利用上面方式时,属性名就是 close-conn 而不是 closeConn,所以需要额外的一步</span>
        properties.putAll(<span class="hljs-built_in">this</span>.properties.getProperties());
        interceptor.setProperties(properties);
        <span class="hljs-keyword">return</span> interceptor;
    }
    

    2.3 yml 配置

    # 分页配置
    pagehelper:
      # 指定使用的数据库数据库
      helperDialect: mysql
      # reasonable:分页合理化参数
      reasonable: true
      # 自动分页 当查询条件有 pageNum,pageSize 参数时,自动执行分页
      support-methods-arguments: false