Mybatis源码解读-SpringBoot中配置加载和Mapper的生成

本文mybatis-spring-boot探讨在springboot工程中mybatis相关对象的注册与加载。

建议先了解mybatis在spring中的使用和springboot自动装载机制,再看此文章。

传送门:Mybatis源码解读-配置加载和Mapper的生成

问题

@MapperScan@Mapper能一起用吗?

使用

  1. 创建工程不再赘述,参考demo

  2. 编写Mapper

    Mapper的注册有两种方式:

    • 在Mapper添加@Mapper注解
    • 在Application类添加@MapperScan注解确定扫描包路径

    后面会讲解这两种方式的区别

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);
}
}
}

SqlSessionTemplateSqlSession的子类,所以现在二者都有了。

Mapper

Mapper的扫描分两种方式讨论

  1. @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<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是没有效果的。

posted @   王谷雨  阅读(499)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示