原创从SpringBoot2.x整合Mybatis-Plus深⼊理解Mybatis解析
Ma。。。
点击上⽅蓝⾊“猿芯”关注我们,输⼊1024,你懂的
背景
最近在使⽤⾼版本Spring Boot 2.x整合mybatis-plus 3.4.1时,控制台出现⼤量的warn提⽰XxxMapper重复定义信息:Bean already defined
with the same name。
2020-12-07 19:37:26.025  WARN 25756 --- [          main] o.m.s.mapper.ClassPathMapperScanner      : Skipping MapperFactoryBean with name 'roleMap
2020-12-07 19:37:26.025  WARN 25756 --- [          main] o.m.s.mapper.ClassPathMapperScanner      : Skipping MapperFactoryBean with name 'userMapper' an 2
虽然这些警告并不影响程序正确运⾏,但是每次启动程序看到控制台输出这些警告⽇志信息,⼼情不是很美丽呀。
于是趁着最近这段空闲时间,快马加鞭动起了我的 “发财” ⼩⼿,撸起袖⼦加油⼲,花了⼀点时间研究了下mybatis-plus如何初始
化mapper对象的相关源代码。
问题分析开挂模式
Maven 依赖
从Bean already defined with the same name警告信息来看,感觉应该是:重复加载 mapper 的 bean 对象定义了。所以我从mybatis-
plus的pom依赖⼊⼿,到mybatis-plus总共依赖三个 jar  包:
1. mybatis-plus-boot-starter  3.4.1
2. mybatis-plus-extension
3.
4.1
3. pagehelper-spring-boot-starter 1.2.10
接着,看了下 mybatis-plus  启动相关配置,发现也没啥⽑病。
mybatis-plus 配置类
@Configuration
@MapperScan(basePackages = "com.dunzung.**.mapper.**")
public class MybatisPlusConfiguration {
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setDbType(DbType.MYSQL);
return paginationInterceptor;
}
}
Service 类定义
⾃定义的MybatisServiceImpl继承了mybatis-plus的ServiceImpl实现类;⾃定义的MybatisService继承了IService接⼝类。
/**
* ⾃定义 Service 接⼝基类
*/
public interface MybatisService<T> extends IService<T> {
}
public interface RoleService extends MybatisService<RoleEntity> {
}
/**
* ⾃定义 Service 实现接⼝基类
*/
public class MybatisServiceImpl<M extends DaoMapper<T>, T> extends ServiceImpl<M, T> implements MybatisService<T> {
}
@Slf4j
@Service
public class RoleServiceImpl extends MybatisServiceImpl<RoleMapper, RoleEntity> implements RoleService {
}
Mapper 类定义
RoleMapper基于注解@Mapper配置,基本上零配置(xml)。
@Mapper
public interface RoleMapper extends DaoMapper<RoleEntity> {
}
上⾯的 mybatis-plus 相关配置⾮常简单,没啥⽑病,所以只能从 mybatis-plus 相关的三个jar源码⼊⼿了。
祖传源代码分析
从⽇志输出信息定位可以看出是o.m.s.mapper.ClassPathMapperScanner打印的警告⽇志,于是在ClassPathMapperScanner类中到了输出警告⽇志的checkCandidate()⽅法:
/**
* {@inheritDoc}
*/
@Override
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
if (super.checkCandidate(beanName, beanDefinition)) {
return true;
} else {
LOGGER.warn(() -> "Skipping MapperFactoryBean with name '" + beanName + "' and '"
+ BeanClassName() + "' mapperInterface" + ". Bean already defined with the same name!");
return false;
}
}
}
打开Debug模式,在ClassPathMapperScanner的checkCandidate()⽅法体打断点,验证该⽅法是否重复调⽤两次。
第⼀次Spring Boot程序启动时会⾃动装配mybatis-spring-boot-autoconfigure这个jar包中的MybatisAutoConfiguration配置类,通过其内部类AutoConfiguredMapperScannerRegistrar  的registerBeanDefinitions()注册bean⽅法,调⽤
了ClassPathMapperScanner的doScan() ⽅法,然后通过checkCandidate()⽅法判断mapper对象是否已注册。
doScan⽅法详细代码如下:
protected Set<BeanDefinitionHolder> basePackages) {
...
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
...
if (checkCandidate(beanName, candidate)) {
...
}
}
}
Tips
checkCandidate()对已注册mapper对象进⾏是否重复定义判断
第⼆次通过MapperScans注解,通过@Import注解,导⼊并调⽤了mybatis-spring-2.0.5这个jar包中MapperScannerConfigurer类的postProcessBeanDefinitionRegistry()⽅法,在postProcessBeanDefinitionRegistry()⽅法中 再⼀次实例化mapper的扫描
类ClassPathMapperScanner,并⼜⼀次调⽤doScan⽅法初始化mapper对象,且也调⽤了checkCandidate()⽅法,从⽽有了⽂章开头⽇志输出的Bean already defined with the same name警告信息。
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
...
scanner.scan(
}
Debug调试到这⾥,⼤致猜到是mybatis-plus相关jar包有bug了,主要涉及两个jar:
第⼀个是mybatis-spring-boot-autoconfigure,主要是⽤于spring⾃动装配mybatis相关初始化配置,mybatis⾃动装配配置类
是MybatisAutoConfiguration。
解决问题我是有原则的
从上⾯的debug调试代码分析可以得出,mapper确实被实例化了2次,也验证了我当初的判断。
那为什么会这样呢?
我们不妨先把⼯程依赖的pagehelper-spring-boot-starter升级最新版到1.3.0版本,mybatis-plus-boot-starter和mybatis-plus-extension已经是最新版本3.4.1,再次Application启动警告尽然⾃动消失了。
这⾥我对⽐了在mybatis-spring-boot-autoconfigure包中MybatisAutoConfiguration所属内部类AutoConfiguredMapperScannerRegistrar的registerBeanDefinitions()⽅法,发现1.3.2版本和2.1.3版本的代码实现区别⾮常⼤,⼏乎是重写了该⽅法。
springboot是啥
mybatis-spring-boot-autoconfigure 的 1.3.2 版本写法
/**
* This will just scan the same base package as Spring Boot does. If you want
* more power, you can explicitly use
* {@batis.spring.annotation.MapperScan} but this will get typed
* mappers working correctly, out-of-the-box, similar to using Spring Data JPA
* repositories.
*/
public static class AutoConfiguredMapperScannerRegistrar
implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {      logger.debug("Searching for mappers annotated with @Mapper");
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
try {
if (sourceLoader != null) {
scanner.sourceLoader);
}
List<String> packages = (this.beanFactory);
if (logger.isDebugEnabled()) {
for (String pkg : packages) {
logger.debug("Using auto-configuration base package '{}'", pkg);
}
}
scanner.setAnnotationClass(Mapper.class);
scanner.StringArray(packages));
} catch (IllegalStateException ex) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
}
}
}
/**
* {@batis.spring.annotation.MapperScan} ultimately ends up
* creating instances of {@link MapperFactoryBean}. If
* {@batis.spring.annotation.MapperScan} is used then this
* auto-configuration is not needed. If it is _not_ used, however, then this
* will bring in a bean registrar and automatically register components based
* on the same component-scanning path as Spring Boot itself.
*/
@t.annotation.Configuration
@Import({ AutoConfiguredMapperScannerRegistrar.class })
@ConditionalOnMissingBean(MapperFactoryBean.class)
public static class MapperScannerRegistrarNotFoundConfiguration {
@PostConstruct
public void afterPropertiesSet() {
logger.debug("No {} found.", Name());
}
}
}
mybatis-spring-boot-autoconfigure 的 2.1.3 版本写法

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。