mybatis如何与spring的结合

news2024/10/3 0:26:41

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直接返回false
      if (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 #59
    definition.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.coloradmin.cn/o/2185283.html

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

相关文章

掌控物体运动艺术:图扑 Easing 函数实践应用

现如今&#xff0c;前端开发除了构建功能性的网站和应用程序外&#xff0c;还需要创建具有吸引力且尤为流畅交互的用户界面&#xff0c;其中动画技术在其中发挥着至关重要的作用。在数字孪生领域&#xff0c;动画的应用显得尤为重要。数字孪生技术通过精确模拟现实世界中的对象…

【C++】树形结构的关联式容器:set、map、multiset、multimap的使用

&#x1f33b;个人主页&#xff1a;路飞雪吖~ ✨专栏&#xff1a;C/C 目录 一、set的简单介绍和使用 &#x1f31f;set的介绍 &#x1f525;注意&#xff1a; &#x1f320;小贴士&#xff1a; &#x1f31f;set的使用 ✨set的构造 ✨set的迭代器 ​编辑 ✨set的容量 …

结构光—格雷码构造代码

本篇文章主要给出生成格雷码的代码&#xff0c;鉴于自身水平所限&#xff0c;如有错误&#xff0c;欢迎批评指正。&#xff08;欢迎进Q群交流&#xff1a;874653199&#xff09; #include <iostream> #include <fstream> #include <Windows.h>using…

vue2老项目打包优化:优化脚本生成的代码

前言 上次讲到在一个 vue-cli 的老项目中&#xff0c;修改 vue.config.js 的以下参数&#xff0c;将打包时间从 40min &#xff0c;降到了 12min {parallel: true, // 多核处理&#xff0c;按理说默认应该生效&#xff0c;但我的文件被设置成了falseruntimeCompiler: false, …

spring学习日记-day7-整合mybatis

一、学习目标 spring整合MyBatis的原理主要涉及到将MyBatis的Mapper映射文件交由Spring容器管理&#xff0c;并将其注入到MyBatis的SqlSessionFactory中&#xff0c;从而实现两者的整合。 二、整合mybatis 1.写一个mybatis测试案例 项目结构&#xff1a; 1.数据库 CREATE DA…

高考技术——pandas使用

百家讲坛&#xff0c;谈论古今&#xff0c;今天我们不聊别的&#xff0c;我们来聊一聊中国的国宝——大熊猫&#xff08;bushi&#xff09; 好好&#xff0c;言归正传&#xff0c;我们今天来讲pandas import pandas as pd 申明无需多言&#xff0c;高考主要考察Series和Data…

区块链媒体推广:15个数字解读未来-华媒舍

区块链技术性作为一种区块链技术和加密的数据帐簿技术性&#xff0c;正在逐步引起广泛关注。伴随着新闻媒体市场的发展&#xff0c;区块链媒体推广也成为了新的发展趋势。下面我们就带大家探寻15个数字&#xff0c;揭露将来区块链媒体推广的新方向。 1、网络传播年增长率 数字…

Mac 网络连接正常,微信可以使用,但浏览器打不开网页?

解决&#xff1a; Step1&#xff0c;选择&#x1f34e;图标&#xff0c;选择系统设置&#xff08;或系统偏好设置&#xff09;打开&#xff1b; Step2&#xff0c;选择网络&#xff0c;Wi-Fi Step3&#xff0c;选择详细信息&#xff1b; Step4: 选择代理&#xff0c;关闭右…

每日OJ题_牛客_JOR26最长回文子串_C++_Java

目录 牛客_OR26最长回文子串 题目解析 C代码1 C代码2 Java代码 牛客_OR26最长回文子串 最长回文子串_牛客题霸_牛客网 描述&#xff1a; 对于长度为n的一个字符串A&#xff08;仅包含数字&#xff0c;大小写英文字母&#xff09;&#xff0c;请设计一个高效算法&#xf…

Redis: 持久化之RDB和AOF

概述 Redis 有一个高质量的课题&#xff1a;数据安全性与数据可靠性Redis 是一个内存型数据库&#xff0c;数据大部分都是存在内存里面当信息在内存中流通时&#xff0c;Redis 节点突然就故障挂掉当重新启动的时候&#xff0c;内存中的数据肯定是全部丢失了如果在这种情况下&a…

MySQL 中如何优化 DISTINCT 查询

一、引言 在 MySQL 数据库中&#xff0c;DISTINCT关键字用于查询结果集中去除重复的行。然而&#xff0c;使用DISTINCT可能会导致查询性能下降&#xff0c;特别是在处理大量数据时。本文将介绍一些优化 MySQL 中DISTINCT查询的方法。 二、理解 DISTINCT 查询的性能影响 &…

Oracle中TRUNC()函数详解

文章目录 前言一、TRUNC函数的语法二、主要用途三、测试用例总结 前言 在Oracle中&#xff0c;TRUNC函数用于截取或截断日期、时间或数值表达式的部分。它返回一个日期、时间或数值的截断版本&#xff0c;根据提供的格式进行截取。 一、TRUNC函数的语法 TRUNC(date) TRUNC(d…

2024/10/2

1 线代内积和外积 2 在 PyTorch 中&#xff0c;x.dot(torch.ones(3)) 是执行向量点积&#xff08;dot product&#xff09;操作的代码。假设 x 是一个一维张量&#xff08;向量&#xff09;&#xff0c;其形状是 (N,)&#xff0c;且 N 应该与 torch.ones(3) 的长度相匹配。具…

查找与排序-插入排序

排序算法可以分为内部排序和外部排序&#xff0c;内部排序是数据记录在内存中进行排序&#xff0c;而外部排序是因排序的数据很大&#xff0c;一次不能容纳全部的排序记录&#xff0c;在排序过程中需要访问外存。常见的内部排序算法有&#xff1a;插入排序、希尔排序、选择排序…

java基础应用-循环控制

1、使用while与自增运算符循环遍历数组 1.1 实例说明 本实例利用自增运算符结合while循环获取每个数组元素的值&#xff0c;然后把它们输出到控制台中。其中自增运算符控制索引变量的递增。程序运行结果如图1所示。 图1 实例运行结果 1.2 实现过程 创建ErgodicArray类&#…

企业网盘预算规划,了解2024年最新价格标准

2024年全球企业云存储市场将增15%&#xff0c;企业网盘收费多样&#xff0c;包括用户数量、存储容量定价及综合功能套餐。ZohoWorkDrive、DropboxBusiness、GoogleWorkspace为主流选择&#xff0c;价格因企业规模、功能需求而异&#xff0c;建议灵活选择套餐和长期合作计划。 一…

yub‘s Algorithmic Adventures_Day3

yub’s Algorithmic Adventures_Day3 有序数组的平方 link&#xff1a;977. 有序数组的平方 - 力扣&#xff08;LeetCode&#xff09; 非递减顺序 一个数列中的元素从左到右依次不减&#xff0c;或者说不降序排列. 比如&#xff1a;1233445&#xff0c;12345. 思路分析 如果…

CORE MVC 过滤器 (筛选器)《2》 TypeFilter、ServiceFilter

TypeFilter、ServiceFilter ServiceFilter vs TypeFilter ServiceFilter和TypeFilter都实现了IFilterFactory ServiceFilter需要对自定义的Filter进行注册&#xff0c;TypeFilter不需要 ServiceFilter的Filter生命周期源自于您如何注册&#xff08;全局、区域&#xff09;&…

vite中sass警告JS API过期

1.问题 在Vite创建项目中引入Sass弹出The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0 - vite中sass警告JS API过期 The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0警告提示表明你当前正在使用的 Dart Sass 版本中&#…

Python画图|渐变背景

Python画图在有些时候&#xff0c;需要使用渐变过度。 在matplotlib官网中&#xff0c;提供了一个为柱状图画渐变背景的案例&#xff0c;我们一同探索一番。 【1】官网教程 点开下述链接&#xff0c;直达官网教程&#xff1a; https://matplotlib.org/stable/gallery/lines…