前言

有天下午正在写代码,测验同学突然和我说“合集列表”呈现问题,当即进行定位经过trace_id找到了ERRO日志,发现是SQL syntax error呈现了两个limit,当即检查mapper.xml检查是不是合并代码时呈现了冲突多合了个limit,可是发现并没有,对比了下开始写的时分的代码没看出问题,尝试本地debug看看问题,也没找到问题,尝试着经过源码来看看,点了下mapper目标,看到了interceptors特点,感觉应该是全局装备发生了改变,看了下pr发现项目中确实增加了个mybatis config:

@Configuration
@MapperScan("com.xxx.xxx.**.mapper")
public class MybatisPlusConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        return paginationInterceptor;
    }
}

正好跟分页相关了,再看看呈现问题的mapper相关代码:

ist<Long> listIdsByOpen(Long uid, Integer opened, boolean homePage, Page<Long> pageParam);
public class Page<T> extends AbstractMultiLevelSortVO implements IPage<T> {
...
}
<if test="pageParam != null">
    LIMIT #{pageParam.offset}, #{pageParam.pageSize}
</if>

看到了IPage就知道了是由于MybatisPlusConfig触发了它。

在排查的时分还发现了@MapperScan的basePackages包名写错了,可是没有影响。

下面咱们收拾下问题,再一一解决。

需求了解的问题

  • 为啥@MapperScan的basePackages包名写错了,可是不受影响?
  • @MapperScan是怎么收效的
  • mapper接口的初始化
  • 为啥装备PaginationInterceptor了撬动了IPage导致多产生了一个limit?

为啥@MapperScan的basePackages包名写错了,可是不受影响

由于在component中有个全局的MybatisAutoConfig:

@Configuration
@ConditionalOnProperty("mybatis.mapper.scan-package")
@MapperScan("${mybatis.mapper.scan-package}")

yml中装备了:

mybatis:
  mapper:
    scan-package: com.xxx.xxx.**.mapper

另外mybatis-plus starter也有主动安装,默许安装的MapperScannerConfigurer需求合作@Mapper注释: MybatisPlusAutoConfiguration#registerBeanDefinitions

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    if (!AutoConfigurationPackages.has(this.beanFactory)) {
        logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
        return;
    }
    logger.debug("Searching for mappers annotated with @Mapper");
    List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
    if (logger.isDebugEnabled()) {
        packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
    }
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);
    builder.addPropertyValue("annotationClass", Mapper.class);
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
    BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
    Stream.of(beanWrapper.getPropertyDescriptors())
        // Need to mybatis-spring 2.0.2+
        .filter(x -> x.getName().equals("lazyInitialization")).findAny()
        .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
    registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}

上面自己定义的,在@MapperScan中没有指定mapper接口需求被特殊注解注释,因而在写mapper接口时不需求加上@Mapper。

@MapperScan是怎么收效的

mybatis-spring供给的一个注解,用于主动扫描指定包下的Mapper接口,并将其注册成Spring容器中的bean。

MapperScannerRegistrar

经过@MapperScan源码能够看到经过@Import动态注册了MapperScannerRegistrar Bean发现其为中心处理类:

package org.mybatis.spring.annotation;
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 取得@MapperScan注解的特点,能够看到value值为咱们指定的,factoryBean为默许的MapperFactoryBean用于后边给dao bean生成署理运用。
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }
}

能够看到:

  • 完成了ImportBeanDefinitionRegistrar,在处理@Configuration类时,能够运用它注册额定的BeanDefinitions,并将它注册到Spring容器中。

    @MapperScan经过@Import将MapperScannerRegistrar注册到了Spring容器中。

  • registerBeanDefinitions办法的入参为BeanDefinitionRegistry,所以不仅能够创立还能够删去和修正BeanDefinitions。

    这儿MapperScannerRegistrar#registerBeanDefinitions的importingClassMetadata为咱们的装备类——MybatisPlusConfig,BeanDefinitionRegistry为DefaultLisableBeanFactory。

继续调用的办法registerBeanDefinitions(LAnnotationMetadata,LAnnotationAttributes;LBeanDefinitionRegistry;LString)

void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
    BeanDefinitionRegistry registry, String beanName) {
  // #1
  BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  builder.addPropertyValue("processPropertyPlaceHolders", true);
  // #2
  Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
  if (!Annotation.class.equals(annotationClass)) {
    builder.addPropertyValue("annotationClass", annotationClass);
  }
  Class<?> markerInterface = annoAttrs.getClass("markerInterface");
  if (!Class.class.equals(markerInterface)) {
    builder.addPropertyValue("markerInterface", markerInterface);
  }
  Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
  if (!BeanNameGenerator.class.equals(generatorClass)) {
    builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
  }
  Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
  if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
    builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
  }
  String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
  if (StringUtils.hasText(sqlSessionTemplateRef)) {
    builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
  }
  String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
  if (StringUtils.hasText(sqlSessionFactoryRef)) {
    builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
  }
  //能够看到多种方式都进行合并了
  List<String> basePackages = new ArrayList<>();
  basePackages.addAll(
      Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
  basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
      .collect(Collectors.toList()));
  basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
      .collect(Collectors.toList()));
  if (basePackages.isEmpty()) {
    basePackages.add(getDefaultBasePackage(annoMeta));
  }
  String lazyInitialization = annoAttrs.getString("lazyInitialization");
  if (StringUtils.hasText(lazyInitialization)) {
    builder.addPropertyValue("lazyInitialization", lazyInitialization);
  }
  builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
  // #3
  registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
  1. 获取了一个MapperScannerConfigurer BeanDefinitionBuilder:
public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    ...
} 

能够看到MapperScannerConfigurer是一个BeanFactotyPostProcessor,从指定包下开始递归搜索接口,并将其注册为MapperFactoryBean。

  1. 获取@MapperScan的annotationClass特点,这儿是默许的Annotation,因而Mapper接口无需还要有任何其他注解。
  2. 这儿向BeanFactory中注册了beanName为com.onepiece.picture.MybatisPlusConfig#MapperScannerRegistrar#0,BeanDefinition为MapperScannerConfigurer。

继续往下跟MapperScannerConfigurer。

MapperScannerConfigurer

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  if (this.processPropertyPlaceHolders) {
    // #1
    processPropertyPlaceHolders();
  }
  // #2
  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  scanner.setAddToConfig(this.addToConfig);
  scanner.setAnnotationClass(this.annotationClass);
  scanner.setMarkerInterface(this.markerInterface);
  scanner.setSqlSessionFactory(this.sqlSessionFactory);
  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  scanner.setResourceLoader(this.applicationContext);
  scanner.setBeanNameGenerator(this.nameGenerator);
  scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  if (StringUtils.hasText(lazyInitialization)) {
    scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  }
  // # 3
  scanner.registerFilters();
  // #4
  scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
  1. 处理占位符获取替换为真实的值,比方下面将${mybatis.mapper.scan-package}替换为装备中的值:
@MapperScan("${mybatis.mapper.scan-package}")

2. 这儿创立了一个ClassPathMapperScanner目标,传递了BeanDefinitionRegistry便于后续扫描mapper接口进行注册

ClassPathMapperScanner坐落mybatis-spring包中继承了ClassPathBeanDefinitionScanner(运用供给的BeanDefinitionResgistry和classpath注册相应的bean definitions),

3. 过滤出想要的mapper接口:

增加包括、扫除的过滤器,比方@MapperScan能够指定接口需求注释哪个注解、完成哪个接口
public void registerFilters() {
  boolean acceptAllInterfaces = true;
  // if specified, use the given annotation and / or marker interface
  if (this.annotationClass != null) {
    addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
    acceptAllInterfaces = false;
  }
  // override AssignableTypeFilter to ignore matches on the actual marker interface
  if (this.markerInterface != null) {
    addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
      @Override
      protected boolean matchClassName(String className) {
        return false;
      }
    });
    acceptAllInterfaces = false;
  }
  if (acceptAllInterfaces) {
    // default include filter that accepts all classes
    addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
  }
  // exclude package-info.java
  addExcludeFilter((metadataReader, metadataReaderFactory) -> {
    String className = metadataReader.getClassMetadata().getClassName();
    return className.endsWith("package-info");
  });
}

4. 进行扫描注册:

ClassPathBeanDefinitionScanner#scan:

public int scan(String... basePackages) {
   int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
   doScan(basePackages);
   // Register annotation config processors, if necessary.
   if (this.includeAnnotationConfig) {
      AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
   }
   return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

ClassPathMapperScanner#doScan

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  // #1  
  Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
  if (beanDefinitions.isEmpty()) {
    LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
        + "' package. Please check your configuration.");
  } else {
    // #2
    processBeanDefinitions(beanDefinitions);
  }
  return beanDefinitions;
}
  1. 运用父类ClassPathBeanDefinitionScanner#doScan获取包下BeanDefinition:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   for (String basePackage : basePackages) {
      // 经过给定的包扫描classes下面一切符合的mapper接口
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      for (BeanDefinition candidate : candidates) {
         ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
         candidate.setScope(scopeMetadata.getScopeName());
         String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
         if (candidate instanceof AbstractBeanDefinition) {
            postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
         }
         if (candidate instanceof AnnotatedBeanDefinition) {
            AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
         }
         if (checkCandidate(beanName, candidate)) {
            BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
            definitionHolder =
                  AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
            beanDefinitions.add(definitionHolder);
            registerBeanDefinition(definitionHolder, this.registry);
         }
      }
   }
   return beanDefinitions;
}

2. 对取得的beanDefinitions进行处理:

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();
    String beanClassName = definition.getBeanClassName();
    LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
        + "' mapperInterface");
    // the mapper interface is the original class of the bean
    // but, the actual class of the bean is MapperFactoryBean
    // #1
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
    // #2
    definition.setBeanClass(this.mapperFactoryBeanClass);
    // #3
    definition.getPropertyValues().add("addToConfig", this.addToConfig);
    boolean explicitFactoryUsed = false;
    if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
      definition.getPropertyValues().add("sqlSessionFactory",
          new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
      explicitFactoryUsed = true;
    } else if (this.sqlSessionFactory != null) {
      definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
      explicitFactoryUsed = true;
    }
    if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
      if (explicitFactoryUsed) {
        LOGGER.warn(
            () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
      }
      definition.getPropertyValues().add("sqlSessionTemplate",
          new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
      explicitFactoryUsed = true;
    } else if (this.sqlSessionTemplate != null) {
      if (explicitFactoryUsed) {
        LOGGER.warn(
            () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
      }
      definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
      explicitFactoryUsed = true;
    }
    if (!explicitFactoryUsed) {
      LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
    definition.setLazyInit(lazyInitialization);
  }
}

a. 把原生的className放在了构造器参数里,方便后边署理找到被署理类
b. 这儿的把原来的mapper接口class替换成了class org.mybatis.spring.mapper.MapperFactoryBean
c. 增加了个特点“addToConfig”值为true,标记下是否用来加入org.apache.ibatis.session.Configuration:

MybatisPlus两个limit引发的思考(上)——@MapperScan是如何生效的

MybatisPlus两个limit引发的思考(上)——@MapperScan是如何生效的

MybatisPlus两个limit引发的思考(上)——@MapperScan是如何生效的

到这儿@MapperScan的效果就完成了,根据basePackages扫描指定包下mapper接口并注册为Spring bean,beanclass为MapperFactroyBean(用于动态生成Mapper接口的署理实例)。

MybatisPlus两个limit引发的思考(上)——@MapperScan是如何生效的

时序图

MybatisPlus两个limit引发的思考(上)——@MapperScan是如何生效的
简单描述下大概步骤:

  1. 在spring容器初始化的时分会在invokeBeanFactoryPostProcessors阶段会加载装备类MybatisPlusConfig
  2. 发现装备类上有@Import(MapperScannerRegistrar.class)从而履行它的registerBeanDefinitions注册了MapperScannerConfigurer
  3. 履行MapperScannerConfigurer调用ClassPathMapperScanner进行扫描并注册包下一切符合的BeanDefinition并对其进行处理,终究注册的bean定义class为MapperFactoryBean。

整个流程都在AbstractApplicationContext中的invokeBeanFactoryPostProcessors中进行,中心类有:

  1. MapperScannerRegistrar:是一种ImportBeanDefinitionRegistrar,注册了MapperScannerConfigurer
  2. MapperScannerConfigurer:是一种BeanFactoryPostProcessor,创立了ClassPathMapperScanner并把自己的特点赋值给了它(会把占位符替换为实际的值)
  3. ClassPathMapperScanner:用户扫描并注册包下一切符合BeanDefinition并对其进行处理。

接下来咱们继续看看后续怎么运用这些bean definition生成署理实例的。