前言
有天下午正在写代码,测验同学突然和我说“合集列表”呈现问题,当即进行定位经过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());
}
- 获取了一个MapperScannerConfigurer BeanDefinitionBuilder:
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
...
}
能够看到MapperScannerConfigurer是一个BeanFactotyPostProcessor,从指定包下开始递归搜索接口,并将其注册为MapperFactoryBean。
- 获取@MapperScan的annotationClass特点,这儿是默许的Annotation,因而Mapper接口无需还要有任何其他注解。
- 这儿向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));
}
- 处理占位符获取替换为真实的值,比方下面将${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;
}
- 运用父类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:
到这儿@MapperScan的效果就完成了,根据basePackages扫描指定包下mapper接口并注册为Spring bean,beanclass为MapperFactroyBean(用于动态生成Mapper接口的署理实例)。
时序图
简单描述下大概步骤:
- 在spring容器初始化的时分会在invokeBeanFactoryPostProcessors阶段会加载装备类MybatisPlusConfig
- 发现装备类上有
@Import(MapperScannerRegistrar.class)
从而履行它的registerBeanDefinitions注册了MapperScannerConfigurer - 履行MapperScannerConfigurer调用ClassPathMapperScanner进行扫描并注册包下一切符合的BeanDefinition并对其进行处理,终究注册的bean定义class为MapperFactoryBean。
整个流程都在AbstractApplicationContext中的invokeBeanFactoryPostProcessors中进行,中心类有:
- MapperScannerRegistrar:是一种ImportBeanDefinitionRegistrar,注册了MapperScannerConfigurer
- MapperScannerConfigurer:是一种BeanFactoryPostProcessor,创立了ClassPathMapperScanner并把自己的特点赋值给了它(会把占位符替换为实际的值)
- ClassPathMapperScanner:用户扫描并注册包下一切符合BeanDefinition并对其进行处理。
接下来咱们继续看看后续怎么运用这些bean definition生成署理实例的。