mybatis如何与spring的结合

1.前言

在现在的java项目开发中,MyBatis和Spring是两个非常流行的框架。MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。而Spring则是一个广泛使用的开源框架,用于构建企业级应用程序。将这两个框架整合在一起,可以充分利用它们各自的优势,构建出高效、易于维护的数据访问层。

2.整合关键组件与技术

我们在代码的开发过程中,只需要引入对应的jar包的pom文件,并在spring的java文件中引入了@MapperScan注解,并在@MapperScan注解中指定对应的基础包类就可以了。剩下的只需要在基础包下进行创建对应的Mapper接口文件就可以了。

那么问题来了,

1.spring是什么时候进行加载这些接口文件呢

2.通过什么方式进行加载文件呢。

3. 加载的文件都是接口,那如何对这些接口进行初始化呢

针对上面的三个问题,我们来点击进入@MapperScan注解查看一些Mybatis与Spring的之间的“勾当”

2.1@MapperScan注解

下面的代码是MapperScan注解的注解,其中不少的代码因为不涉及此次分析,我都进行了隐藏。在下面的代码中我们可以看到一切的解密应该都在MapperScannerRegistrar这个类中。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {String[] value() default {};String[] basePackages() default {};
}

MapperScannerRegistrar类,这个类继承了ImportBeanDefinitionRegistrar这个类,ImportBeanDefinitionRegistrar这个类呢在Spring的启动的过程中,是一个比较重要的类,现在简单的说,ImportBeanDefinitionRegistrar这个类在spring启动的过程中,会进行执行registerBeanDefinitions方法,感兴趣的同学,可以看我前面发表的文章:spring组件动态注册Bean。那么在MapperScannerRegistrar这个类做了什么呢,我觉得最重要的两行代码是:

BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);   
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

上面两行代码的主要含义是:mybatis组件向spring容器进行注册了一个MapperScannerConfigurer对象。

那么这个MapperScannerConfigurer对象会在spring的容器初始化的时候进行执行一些操作。具体的执行时机,也可以看我前面发表的文章:spring组件动态注册Bean。

MapperScannerRegistrar中的详细代码源码如下:

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));if (mapperScanAttrs != null) {this.registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));}}void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);builder.addPropertyValue("processPropertyPlaceHolders", true);Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");if (!Annotation.class.equals(annotationClass)) {builder.addPropertyValue("annotationClass", annotationClass);}Class<?> markerInterface = annoAttrs.getClass("markerInterface");if (!Class.class.equals(markerInterface)) {builder.addPropertyValue("markerInterface", markerInterface);}Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");if (!BeanNameGenerator.class.equals(generatorClass)) {builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));}Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);}String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");if (StringUtils.hasText(sqlSessionTemplateRef)) {builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));}String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");if (StringUtils.hasText(sqlSessionFactoryRef)) {builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));}List<String> basePackages = new ArrayList();basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));if (basePackages.isEmpty()) {basePackages.add(getDefaultBasePackage(annoMeta));}String lazyInitialization = annoAttrs.getString("lazyInitialization");if (StringUtils.hasText(lazyInitialization)) {builder.addPropertyValue("lazyInitialization", lazyInitialization);}String defaultScope = annoAttrs.getString("defaultScope");if (!"".equals(defaultScope)) {builder.addPropertyValue("defaultScope", defaultScope);}builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));registry.registerBeanDefinition(beanName, builder.getBeanDefinition());}}

2.2 MapperScannerConfigurer类的说明

MapperScannerConfigurer类 实现了BeanDefinitionRegistryPostProcessor类中的postProcessBeanDefinitionRegistry方法,这个方法会在spring启动的过程中进行执行.

在这个方法中,我们可以看到mybatis进行实例化了一个ClassPathMapperScanner(扫描器),这块不知道大家有没有这样的疑问,那就是mybatis为啥要进行实例化出来一个扫描器,为啥不用spring自己的扫描器呢,原理是因为spring的扫描器,mybatis用不了,为啥用不了呢,因为spring的扫描器,扫描的是加了@Component,@ManagedBean,@Name等注解的非接口类。这点针对mybatis而言是不适用的。所以mybatis自己创建了一个新的扫描器:ClassPathMapperScanner

MapperScannerConfigurer类中的核心代码块:

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {if (this.processPropertyPlaceHolders) {this.processPropertyPlaceHolders();}ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);scanner.setAddToConfig(this.addToConfig);scanner.setAnnotationClass(this.annotationClass);scanner.setMarkerInterface(this.markerInterface);scanner.setSqlSessionFactory(this.sqlSessionFactory);scanner.setSqlSessionTemplate(this.sqlSessionTemplate);scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);scanner.setResourceLoader(this.applicationContext);scanner.setBeanNameGenerator(this.nameGenerator);scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);if (StringUtils.hasText(this.lazyInitialization)) {scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));}if (StringUtils.hasText(this.defaultScope)) {scanner.setDefaultScope(this.defaultScope);}scanner.registerFilters();scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));}
}

2.3 ClassPathMapperScanner类

ClassPathMapperScanner这个类是继承于spring的内置的扫描器ClassPathBeanDefinitionScanner,关于spring的ClassPathBeanDefinitionScanner类中有两个比较重要的属性includeFilters和excludeFilters这两个属性都为List集合,从字面意思上我们就可以看出来,includeFilters这个Filter的集合就是决定哪些类能被扫描到,excludeFilters这的Filter的集合来决定哪些类不能被扫描到。

2.3.1 扫描过滤器设置

我们从构造函数中可以看到,ClassPathMapperScanner类在进行初始化的时候,useDefaultFilters属性传入的是false,那么可以证明ClassPathMapperScanner将不使用spring默认的includeFilters和excludeFilters。

在上面MapperScannerConfigurer类中,我们看到了 scanner.registerFilters();这行代码,说明在ClassPathMapperScanner初始化完成之后,调用了registerFilters方法。有自己的includeFilters和excludeFilters。

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner{public ClassPathMapperScanner(BeanDefinitionRegistry registry) {super(registry, false);//进行调用父类的构造函数,/****   public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {*     this(registry, useDefaultFilters, getOrCreateEnvironment(registry));*   }  ****/}/**** MapperScan注解的简单示例:** @MapperScan(basePackages = "xxx.xxx.xxx",*       annotationClass =  A.class,*        markerInterface = =TMappper.class)***/public void registerFilters() {boolean acceptAllInterfaces = true;//代表着接口上只有添加了A注解的类 才能被扫描到if (this.annotationClass != null) {this.addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));acceptAllInterfaces = false;}//设置了markerInterface代表着所有的接口都不会进行扫描,因为下面matchClassName直接返回falseif (this.markerInterface != null) {this.addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {protected boolean matchClassName(String className) {return false;}});acceptAllInterfaces = false;}//为true的时候 代表着包下面的所有接口都会进行扫描if (acceptAllInterfaces) {this.addIncludeFilter((metadataReader, metadataReaderFactory) -> {return true;});}//排除了类名以package-info结尾的类 这种情况感觉很少会用到this.addExcludeFilter((metadataReader, metadataReaderFactory) -> {String className = metadataReader.getClassMetadata().getClassName();return className.endsWith("package-info");});}
}

2.3.2 doScan方法的执行

在给ClassPathMapperScanner类初始化完成之后,就会执行ClassPathMapperScanner的scan方法进行扫描类的信息,此处要注意调用的是ClassPathMapperScanner的doScan方法。

  public Set<BeanDefinitionHolder> doScan(String... basePackages) {//调用父类的doscan方法,进行扫描     Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {LOGGER.warn(() -> {return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";});} else {//mybatis扫描的的核心this.processBeanDefinitions(beanDefinitions);}return beanDefinitions;}

2.3.3 ClassPathMapperScanner#processBeanDefinitions方法

这个方法主要是针对扫描出来的BeanDefinition对象,进行一个“狸猫换太子”的思想,其主要核心代码如下:

private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;for (BeanDefinitionHolder holder : beanDefinitions) {definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59definition.setBeanClass(this.mapperFactoryBeanClass);
}

这个代表着什么意思呢,举个例子说明一下:

假如现在有两个mapper,AMapper,BMapper,正常情况下被spring扫描完成后,AMapper对应的class的应该是AMapper.class,BMapper对应的class应该是BMapper.class,但是现在不是了都变成了MapperFactoryBean.class,那么spring在针对AMapper,BMapper进行初始化的时候,就不会进行调用AMapper,BMapper的实例化方法,而是会进行调用MapperFactoryBean类中的getObject方法.通过这个MapperFactoryBean工厂来进行生产Mapper对象。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {public T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}
}

3.总结

那我们来简单总结一下,首先@MapperScan注解中的MapperScannerRegistrar类,往spring容器中添加了MapperScannerConfigurer类;其次 MapperScannerConfigurer类在spring的启动过程中,会进行启动一个ClassPathMapperScanner类的扫描器,该扫描继承于spring内置的扫描器ClassPathBeanDefinitionScanner,覆盖其中的过滤器的逻辑和doscan方法来进行扫描mapper信息;最后将扫描出来的Mapper信息进行“狸猫换太子”,使用mybatis的MapperFactoryBean类来进行替换对用的mapper的Class信息,在进行构建对应的Mapper的时候 其实就是调用MapperFactoryBean工厂类来进行生产对应的Mapper。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1553179.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

基于SSM的高校勤工助学管理系统的设计与实现(源码+定制+参考文档)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

linux文件编程_线程

1. 基本概念 1.1. 进程与线程的概念 典型的UNIX/linux进程可以看成是只有一个控制线程&#xff0c;一个进程在同一时刻只做一件事情&#xff0c;有了多个控制线程后&#xff0c;在程序设计时可以把进程设计成在同一时刻做不止一件事&#xff0c;每个线程各自处理独立的任务。…

【重学 MySQL】五十、添加数据

【重学 MySQL】五十、添加数据 使用INSERT INTO语句添加数据基本语法示例插入多行数据注意事项 使用LOAD DATA INFILE语句批量添加数据其他插入数据的方式注意事项 在MySQL中&#xff0c;添加数据是数据库操作中的基本操作之一。 使用INSERT INTO语句添加数据 使用 INSERT IN…

多维度柱状图绘制

图形结果 绘制过程 数据如下 调整柱子宽度 Z轴设置 、 配色表

计算机网络:计算机网络体系结构 —— 专用术语总结

文章目录 专用术语实体协议服务服务访问点 SAP 服务原语 SP 协议数据单元 PDU服务数据单元 SDU 专用术语 实体 实体是指任何可以发送或接收信息的硬件或软件进程 对等实体是指通信双方处于相同层次中的实体&#xff0c;如通信双方应用层的浏览器进程和 Web 服务器进程。 协…

C++设计模式之观察者模式

一、观察者模式概念 观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。这种模式通常用于实现分布式事件处理系统,当一个对象(称为“主题”或“发布者”)改变状…

C动态内存管理

前言&#xff1a;不知不觉又过去了很长的一段时间。今天对C语言中的动态内存管理进行一个系统性的总结。 1 为什么要有动态内存分配 在C语言中&#xff0c;使用int&#xff0c;float&#xff0c;double&#xff0c;short等数据内置类型以及数组不是也可以开辟内存空间吗&…

【光伏混合储能】VSG并网运行,构网型变流器,虚拟同步机仿真

摘要 本文提出了一种基于光伏发电与混合储能系统结合的虚拟同步发电机&#xff08;VSG&#xff09;控制策略&#xff0c;该策略能够在并网运行时稳定电网电压和频率。通过仿真分析&#xff0c;验证了该策略在各种运行工况下的有效性&#xff0c;展示了其在电力系统中的广泛应用…

了解芯片光刻与OPC

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 参考资料&#xff1a; 光刻技术与基本流程 https://www.bilibili.com/video/BV1tP4y1j7BA OPC https://www.bilibili.com/video/BV1o94y1U7Td 论文&#xff1a;计算…

CyberBattleSim项目熟悉遇到的问题

在看手册的时候&#xff0c;手册中说需要显卡&#xff0c;配置还不低。 ——师兄说不需要这个显卡&#xff0c;他的独显也能跑&#xff0c;现在能安装配置了&#xff0c;配置文件安装不了确定是否进入了创建的conda环境&#xff0c;多尝试几次。 随着在安装gym的时候&#xf…

【Python报错已解决】TypeError: ‘NoneType‘ object is not callable

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

动手学运动规划: 2.2.c 3次样条曲线代码解析

学习? 学个P!☺ — 亮剑 李云龙 &#x1f3f0;代码及环境配置&#xff1a;请参考 环境配置和代码运行! 本节提供了3次样条曲线的代码测试 python3 tests/curves/cubic_spline.py2.2.c.1 3次样条曲线代码实现 CubicSpline1D实现了1维的3次样条曲线, 需要输入一组离散点. Cub…

主存储器——随机存取存储器RAM

静态RAM 双稳态触发器 一、工作特性 两种稳定状态&#xff1a; 双稳态触发器具有两个稳定的输出状态&#xff0c;通常表示为 0 和 1&#xff08;或低电平和高电平&#xff09;。这两个状态可以长期保持&#xff0c;即使在没有输入信号的情况下&#xff0c;也不会自发地改变。 例…

Study-Oracle-10-ORALCE19C-RAC集群搭建

一路走来,所有遇到的人,帮助过我的、伤害过我的都是朋友,没有一个是敌人。 ORACLE --RAC 搭建理念:准备工作要仔细,每个参数及配置都到仔细核对。环境准备完成后,剩下的就是图像化操作,没啥难度,所以图形化操作偷懒不续写了。 一、硬件信息及配套软件 1、硬件设置 RA…

C++初阶:STL详解(十)——priority_queue的介绍,使用以及模拟实现

✨✨小新课堂开课了&#xff0c;欢迎欢迎~✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C&#xff1a;由浅入深篇 小新的主页&#xff1a;编程版小新-CSDN博客 一.priority_queue的介绍 优先级队列被实现…

手把手教你激活水果音乐制作软件FLStudio Producer Edition 24.1.1.4285 All Plugins汉化中文专业版下载

软件介绍 lmage-Line FL Studio 是由 lmage-Line 公司所开发的一款音乐制作软件&#xff0c;又名:水果音乐。你可以使用FL Studio 软件进行编写&#xff0c;编辑&#xff0c;录制&#xff0c;编辑以及混音和母带制作音乐&#xff0c;目前是世界上最受欢迎的音乐制作工具之一。…

【Linux】Shell脚本基础+条件判断与循环控制

目录 一、介绍 1. Linux提供的Shell解析器 2. bash和sh关系 3. Centos默认的Shell解析器是bash 二、定义 1. 变量名的定义规则 2. 等号周围没有空格 3. 查看变量 4. 删除变量 5. 正确地定义数组 6. 将局部环境变量提升为全局 7. 正确选择引号 8. 特殊变量名 三…

python 开发中识别和解决内存泄漏的技巧

Python 的内存管理是非常优秀的&#xff0c;它使用了自动垃圾回收机制。然而&#xff0c;在某些情况下&#xff0c;内存泄漏依然可能发生。这通常是在复杂的对象引用和循环引用的情境下容易出现&#xff0c;特别是涉及全局变量或不当的引用管理时。内存泄漏问题虽然并不常见&am…

Linux线程(二)线程ID及创建线程详解

1.线程ID 就像每个进程都有一个进程 ID 一样&#xff0c;每个线程也有其对应的标识&#xff0c;称为线程 ID。进程 ID 在整个系统中是唯一的&#xff0c;但线程 ID 不同&#xff0c;线程 ID 只有在它所属的进程上下文中才有意义。 进程 ID 使用 pid_t 数据类型来表示&#xf…

记录cocoscreater3.8.x设置2d卡牌圆角

引擎版本&#xff1a;Cocos Creater3.8.3版本 1.在Card节点上添加Mask组件&#xff0c;类型选择 2.在Card节点上绑定CardController.ts脚本 3.在CardController.ts编写圆角脚本&#xff0c;其实就是动态绘制Graphics组件 import { _decorator, Color, Component, Graphics, …