@MapperScan和@Mapper的坑

今天和大佬一起解决个问题. @Autowired 注入的对象和预期不一致. 结果是因为 Mybatis 扫描的问题

@Mapper

此注解是 Mybatis 自动配置时, 默认扫描的注解类

image-20200102153553352

MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

logger.debug("Searching for mappers annotated with @Mapper");

ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

try {
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}

List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
for (String pkg : packages) {
logger.debug("Using auto-configuration base package '{}'", pkg);
}
}

// 扫描 @Mapper 注解的类
scanner.setAnnotationClass(Mapper.class);
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(packages));
} catch (IllegalStateException ex) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
}
}

@MapperScan

设置扫描属性, 用于手动扫描, 可以设置扫描包, 扫描注解等信息

image-20200102153856893

MapperScannerRegistrar#registerBeanDefinitions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}

Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}

Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}

Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}

Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}

scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}

通过上述代码, 可以发现, @MapperScan 扫描的包下,统统都会被扫描(注入 MapperFactoryBean)

问题

如果@MapperScan 默认扫描了全部, @SpringBoot 也扫描全部, 那么这里面的对象是MapperFactoryBean 还是真正的对象?

image-20200102155310133

一个为@Service 扫描, 一个为MybatisScan 扫描注入的 MapperFactoryBean

解决

还是那句话, 不是人家设计的问题, 而是用的问题

为什么Mybatis 要根据扫描包内的路径, 注入所有的类

如果让你设计的话, 你怎么知道扫描的类是不是MyBatis 需要注入的类?

  1. 特殊表示

    此种方式已经有@Mapper 来支撑, 并且无需扫描, 默认加载, 如果再使用@MapperScan, 实在是多此一举

  2. 只扫描 mapper 包

    问题所在就是这里, 如果只是使用方, 如果不多思考一下, 那么就很可能图懒就直接扫描个大包. 就会有问题

个人建议

其实个人建议是能自动的尽量不要手动. 因为自动的在提供方已经准备的非常完善, 不会有问题, 但是恰恰手动操作的部分会有一些人为的遗漏或者考虑不周或者无法实现等问题, 而导致使用方不知道就完犊子

所以个人觉得最好的方式还是@Mapper, 自动就完了


@MapperScan和@Mapper的坑
https://gallrax.github.io/2020/01/02/@MapperScan和@Mapper的坑/
作者
Gallrax
发布于
2020年1月2日
许可协议