本文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是没有效果的。

Mybatis源码解读-SpringBoot中配置加载和Mapper的生成的更多相关文章

  1. Mybatis源码解读-配置加载和Mapper的生成

    问题 Mybatis四大对象的创建顺序? Mybatis插件的执行顺序? 工程创建 环境:Mybatis(3.5.9) mybatis-demo,参考官方文档 简单示例 这里只放出main方法的示例, ...

  2. Mybatis 源码分析--Configuration.xml配置文件加载到内存

    (补充知识点: 1 byte(字节)=8 bit(位) 通常一个标准英文字母占一个字节位置,一个标准汉字占两个字节位置:字符的例子有:字母.数字系统或标点符号) 1.创建SqlSessionFacto ...

  3. Mybatis源码解读-插件

    插件允许对Mybatis的四大对象(Executor.ParameterHandler.ResultSetHandler.StatementHandler)进行拦截 问题 Mybatis插件的注册顺序 ...

  4. spring IOC DI AOP MVC 事务, mybatis 源码解读

    demo https://gitee.com/easybao/aop.git spring DI运行时序 AbstractApplicationContext类的 refresh()方法 1: pre ...

  5. MyBatis源码解读之延迟加载

    1. 目的 本文主要解读MyBatis 延迟加载实现原理 2. 延迟加载如何使用 Setting 参数配置 设置参数 描述 有效值 默认值 lazyLoadingEnabled 延迟加载的全局开关.当 ...

  6. MyBatis源码解读(3)——MapperMethod

    在前面两篇的MyBatis源码解读中,我们一路跟踪到了MapperProxy,知道了尽管是使用了动态代理技术使得我们能直接使用接口方法.为巩固加深动态代理,我们不妨再来回忆一遍何为动态代理. 我相信在 ...

  7. wemall app商城源码Android之ListView异步加载网络图片(优化缓存机制)

    wemall-mobile是基于WeMall的android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享wemall app商城源码Android之L ...

  8. 【Spring源码分析】非懒加载的单例Bean初始化过程(下篇)

    doCreateBean方法 上文[Spring源码分析]非懒加载的单例Bean初始化过程(上篇),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下 ...

  9. 【Spring源码分析】非懒加载的单例Bean初始化前后的一些操作

    前言 之前两篇文章[Spring源码分析]非懒加载的单例Bean初始化过程(上篇)和[Spring源码分析]非懒加载的单例Bean初始化过程(下篇)比较详细地分析了非懒加载的单例Bean的初始化过程, ...

随机推荐

  1. 【mq】从零开始实现 mq-12-消息的批量发送与回执

    前景回顾 [mq]从零开始实现 mq-01-生产者.消费者启动 [mq]从零开始实现 mq-02-如何实现生产者调用消费者? [mq]从零开始实现 mq-03-引入 broker 中间人 [mq]从零 ...

  2. SRIO RapidIO (SRIO)协议介绍(-)

    1     导读 1.1    与PCIe的差异 典型的PCIe结构定义了一个以单个中央处理器为核心的计算机系统,比如我们常见的工控机.PXIe机箱控制器.服务器内的IO设备.从系统架构来看,这个结构 ...

  3. 项目下载依赖后面加 -S -D -g 分别代表什么意思

    npm install name -S此依赖是在package的dependencies中,不仅在开发中,也在打包上线后的生产环境中,比如vue npm install name -D此依赖是在pac ...

  4. C++primer第二章

    第二章 :变量和基本类型 2.1 基本内置类型 C++定义了一套包含算术类型(arithmetic type)和空类型(void)在内的基本数据类型 2.1.1 算术类型 算术类型的分类: 整型(in ...

  5. 面试官:Dubbo怎么实现服务降级,他有什么好处?

    哈喽!大家好,我是小奇,一位热爱分享的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 书接上回,今天周一了,招聘软件 ...

  6. 3. Docker应用

  7. 2021.05.05【NOIP提高B组】模拟 总结

    T1 给你一棵树,要求增加最少的边权是的从根到每一个叶子的长度相等 不能改变原有的最大长度 这是一个贪心:尽可能往深度小的边增加 先预处理出 \(mx_i\) 表示从 \(i\) 到叶子的最大长度 然 ...

  8. 支持向量机SVM(一):基本概念、目标函数的推导

    本文旨在介绍支持向量机(SVM)的基本概念并解释SVM中的一个关键问题: 为什么SVM目标函数中的函数间隔取1? 一.分类问题 给定N个分属两类的样本,给出一个决策边界使得边界一侧只含一种样本(如下图 ...

  9. 《The Tail At Scale》论文详解

    简介 用户体验与软件的流畅程度是呈正相关的,所以对于软件服务提供方来说,保持服务耗时在用户能接受的范围内就是一件必要的事情.但是在大型分布式系统上保持一个稳定的耗时又是一个很大的挑战,这篇文章解析的是 ...

  10. RPA跨系统自动生成采购订单

    1.从开发器启动机器人 2.RPA登录友采云 3.RPA根据筛选条件,导出采购订单 4.RPA请并登录NC 5.RPA把读取到的数据,逐个录入到NC系统中,并存储到Excel中 6.RPA将最终的Ex ...