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
是没有效果的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构