Mybatis源码解读-SpringBoot中配置加载和Mapper的生成
本文mybatis-spring-boot
探讨在springboot工程中mybatis相关对象的注册与加载。
建议先了解mybatis在spring中的使用和springboot自动装载机制,再看此文章。
传送门:Mybatis源码解读-配置加载和Mapper的生成
问题
@MapperScan
和@Mapper
能一起用吗?
使用
-
创建工程不再赘述,参考demo
-
编写Mapper
Mapper的注册有两种方式:
- 在Mapper添加
@Mapper
注解 - 在Application类添加
@MapperScan
注解确定扫描包路径
后面会讲解这两种方式的区别
- 在Mapper添加
SqlSessionFactory和SqlSession
在讨论自动装配方式之前,先看看mybatis最简洁的demo
public static void main(String[] args) throws Exception {
// 配置文件路径
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 1.读取配置,创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2.通过工厂获取SqlSession
SqlSession session = sqlSessionFactory.openSession();
try {
// 3.获取mapper代理对象
StudentMapper mapper = session.getMapper(StudentMapper.class);
// 4.执行查询,此处才真正连接数据库
System.out.println(mapper.selectByName("张三"));
} finally {
// 5.关闭连接
session.close();
}
}
可以看到,首先需要创建SqlSessionFactory和SqlSession,在springboot中,这两者通过自动装配完成。
在mybatis-spring-boot-autoconfigure-x.x.x.jar的spring.factories中,可以看到自动装配注入了MybatisAutoConfiguration
类
public class MybatisAutoConfiguration implements InitializingBean {
......
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
......
}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
}
SqlSessionTemplate
是SqlSession
的子类,所以现在二者都有了。
Mapper
Mapper的扫描分两种方式讨论
-
@MapperScan方式
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan { }
可以看到,导入了MapperScannerRegistrar类
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); if (mapperScanAttrs != null) { registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0)); } } void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); ...... registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); } }
因为MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,所以会被调用registerBeanDefinitions方法,最后注册MapperScannerConfigurer
咱们先记住MapperScannerConfigurer这个类,去看看@Mapper的方式
-
@Mapper方式
在
MybatisAutoConfiguration
中,有这么一段代码@org.springframework.context.annotation.Configuration // 如果满足条件,则导入AutoConfiguredMapperScannerRegistrar @Import(AutoConfiguredMapperScannerRegistrar.class) // 如果MapperFactoryBean和MapperScannerConfigurer都没注册,则满足条件 @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class }) public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { ...... }
我们在@MapperScan方式看到,是已经注册了MapperScannerConfigurer类的。所以,@MapperScan会覆盖@Mapper
继续看看
AutoConfiguredMapperScannerRegistrar
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { ...... BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); builder.addPropertyValue("annotationClass", Mapper.class); ...... registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); } ...... }
可以看到,同样是注册了MapperScannerConfigurer
也就是两种注解方式都是通过MapperScannerConfigurer扫描mapper注册的
-
通用部分
继续追踪MapperScannerConfigurer的调用链
// MapperScannerConfigurer#postProcessBeanDefinitionRegistry public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { ...... ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); ...... // 注册过滤器(@Mapper和@MapperScan的区别体现在这里) scanner.registerFilters(); // 开始扫描bean scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); } public void registerFilters() { boolean acceptAllInterfaces = true; // 如果指定了扫描类型(@Mapper走这里) // annotationClass在前面的AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions被注入 // 就是这段builder.addPropertyValue("annotationClass", Mapper.class); if (this.annotationClass != null) { addIncludeFilter(new AnnotationTypeFilter(this.annotationClass)); acceptAllInterfaces = false; } ...... // 如果没指定扫描类型,则扫描全部(@MapperScan走这里) if (acceptAllInterfaces) { addIncludeFilter((metadataReader, metadataReaderFactory) -> true); } // exclude package-info.java addExcludeFilter((metadataReader, metadataReaderFactory) -> { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info"); }); }
看完了过滤器的注册,继续回到扫描逻辑scanner.scan
// ClassPathMapperScanner#scan(String... basePackages) --> // ClassPathMapperScanner#doScan(String... basePackages) public Set<BeanDefinitionHolder> doScan(String... basePackages) { // 扫描mapper(此时是原始对象) 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 { // 通过MapperFactoryBean类将mapper对象转换成代理对象MapperProxy processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
答案
@MapperScan
和@Mapper
能一起用(不会报错),但是@Mapper
是没有效果的。