本文 mybatis-spring-boot
探讨在springboot工程中mybatis相关对象的注册与加载。
建议先了解mybatis在spring中的使用和springboot自动装载机制,再看此文章。
@MapperScan
和 @Mapper
能一起用吗?
在讨论自动装配方式之前,先看看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的扫描分两种方式讨论
- @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的方式
2. @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注册的
3. 通用部分 继续追踪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 doScan(String... basePackages) {
// 扫描mapper(此时是原始对象)
Set 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
是没有效果的。
Original: https://www.cnblogs.com/konghuanxi/p/16277509.html
Author: 王谷雨
Title: Mybatis源码解读-SpringBoot中配置加载和Mapper的生成
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/620932/
转载文章受原作者版权保护。转载请注明原作者出处!