Mybatis与Spring结合深探——MapperFactoryBean的奥秘

文章目录

    • 前言
    • MapperFactoryBean的工作原理
    • 底层实现剖析
      • MapperFactoryBean的checkDaoConfig()方法
        • 总结
      • MapperFactoryBean的getObject()方法
    • 思考联想
    • 后续

系列相关相关文章
究竟FactoryBean是什么?深入理解Spring的工厂神器
超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?
Mybatis与Spring结合深探——MapperFactoryBean的奥秘
后续TODO:MapperScannerConfigurer

前言

在这里插入图片描述

在没有Spring单独使用Mybatis的时候,我在之前的文章超硬核解析Mybatis动态代理原理!只有接口没实现也能跑? 讲解到了调用链路new SqlSessionFactoryBuilder().build(xml)-->XMLConfigBuilder#parse-->>XMLConfigBuilder#parseConfiguration--->XMLConfigBuilder#mapperElement-->XMLMapperBuilder#mapperParser.parse()-->XMLMapperBuilder#configurationElement-->XMLMapperBuilder#bindMapperForNamespace-->Configuration#MapperRegistry#addMappper()

在SqlSessionFactoryBuilder().build方法 中最终调用Configuration对象的addMappper()方法(实际上是委托给MapperRegistry的addMapper)添加对应的MapperProxyFactory代理工厂类,最终通过这个工厂类生成对应的代理对象MapperProxy 。

也就是MapperRegistry内部维护一个映射关系,每个接口对应一个MapperProxyFactory(生成动态代理工厂类)

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

这样便于在后面调用MapperRegistry的getMapper()时,直接从Map中获取某个接口对应的动态代理工厂类,然后再利用工厂类针对其接口生成真正的动态代理类。


如果想了解什么是FactoryBean是什么,可以查看前文究竟FactoryBean是什么?深入理解Spring的工厂神器

更详细的内容可以查看我之前的文章:超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?


而我们现在在Spring框架中整合Mybatis时,我们通常会使用MapperFactoryBean来生成Mapper的代理实例,也就是不需要再通过new SqlSessionFactoryBuilder().build(xml)的方式去注册动态代理接口。这是一种更简单且易于配置的方式,可让我们以Spring的形式操作Mybatis的持久层。本文将深入探索MapperFactoryBean的工作原理,并说明如何将Mybatis和Spring框架结合起来,以构建一个响应迅速而又易于维护的数据访问层。

MapperFactoryBean的工作原理

当应用启动时,Spring容器会为每个MapperFactoryBean生成一个相应的Bean实例。这个过程包含了几个关键步骤:

  • Bean的定义:在Spring配置文件中定义MapperFactoryBean,这包括指定其sqlSessionFactorysqlSessionTemplate
  • Bean的实例化:Spring容器将调用MapperFactoryBeangetObject()方法,这个方法内部又会调用Mybatis的SqlSession.getMapper()
  • 生成Mapper代理:正如前面提到的,Mybatis使用动态代理技术生成代理对象。这个过程由Mybatis内部的MapperProxyFactory完成。
  • Bean的使用:最终创建的Mapper被注入到其他组件中,这样,业务代码就可以通过普通的Java方法调用来执行SQL操作了。

下面我们看看实际的配置代码示例:

<!-- Mybatis的SqlSessionFactory配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="mapperLocations" value="classpath*:mapper/*.xml" />
</bean>

<!-- Mapper接口对应的FactoryBean配置 -->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.example.mapper.UserMapper" />
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

当然,在Spring的Java配置中,我们通常用注解来代替上述XML配置,得益于Spring的@MapperScan,可以大幅简化这个配置:

@Configuration
@MapperScan("com.example.mapper")
public class AppConfig {
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        // ...其他配置...
        return sessionFactory.getObject();
    }
}

底层实现剖析

MapperFactoryBean的checkDaoConfig()方法

MapperFactoryBean本身extend自SqlSessionDaoSupport,SqlSessionDaoSupport又extend自DaoSupport接口,DaoSupport接口实现了InitializingBean接口,在对象初始化的时候会调用它的afterPropertiesSet方法,该方法中首先调用了checkDaoConfig()方法,MapperFactoryBean重载的checkDaoConfig()如下面所示—>这里最终会将调用configuration.addMapper(this.mapperInterface)(实际也是委托给MapperRegistry)

微信公众号:bugstack虫洞栈 & MapperFactoryBean类图

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    
    private Class<T> mapperInterface;
    
    public void setMapperInterface(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

      protected void checkDaoConfig() {
        super.checkDaoConfig();
        Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
        Configuration configuration = this.getSqlSession().getConfiguration();
        if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
            try {
                configuration.addMapper(this.mapperInterface);
            } catch (Exception var6) {
                this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
                throw new IllegalArgumentException(var6);
            } finally {
                ErrorContext.instance().reset();
            }
        }

    }

    @Override
    public T getObject() throws Exception {
        return getSqlSession().getMapper(this.mapperInterface);
    }

    // ...
}

checkDaoConfig方法中,会检查mapperInterface是否已设置,符合Spring管理Bean生命周期的要求。

接着通过configuration.addMapper(this.mapperInterface)方法重点关注,最终实现是在MapperRegistry中:
到这里以后,跟我之前文章超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?解析的步骤又是一样的了

new SqlSessionFactoryBuilder().build(xml)-->XMLConfigBuilder#parse-->>XMLConfigBuilder#parseConfiguration--->XMLConfigBuilder#mapperElement-->XMLMapperBuilder#mapperParser.parse()-->XMLMapperBuilder#configurationElement-->XMLMapperBuilder#bindMapperForNamespace-->Configuration#MapperRegistry#addMappper()

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

parser.parse()完成了对mapper对应xml的解析成MappedStatement,并添加到了configuration对象中,这里的configuration也就是我们上面提到的new Configuration()创建的那个对象(非常重要)。

总结

mapper接口的定义在bean加载阶段会被替换成MapperFactoryBean类型,在spring容器初始化的时候会给我们生成MapperFactoryBean类型的对象,在该对象生成的过程中调用afterPropertiesSet()方法,为我们生成了一个MapperProxyFactory类型的对象存放于Configuration里的MapperRegistry对象中,同时解析了mapper接口对应的xml文件,把每一个方法解析成一个MappedStatement对象,存放于Configuration里mappedStatements这个Map集合中。

MapperFactoryBean的getObject()方法

MapperFactoryBean实现了FactoryBean接口,实现了FactoryBean接口的类型在调用getBean(beanName)既通过名称获取对象时,返回的对象不是本身类型的对象,而是通过实现接口中的getObject()方法返回的对象。

这里需要对spring的声明周期有一定的了解:下面是简化版的MapperFactoryBean的链路调用

getObject()--->doGetBean()--->getObjectForBeanInstance()--->getObjectFromFactoryBean()--->doGetObjectFromFactoryBean--->MapperFactoryBean.getObject()方法

protected <T> T doGetBean(final String name, final Object[] args) {
        Object sharedInstance = getSingleton(name);
        if (sharedInstance != null) {
            // 如果是 FactoryBean,则需要调用 FactoryBean#getObject
            return (T) getObjectForBeanInstance(sharedInstance, name);
        }

        BeanDefinition beanDefinition = getBeanDefinition(name);
        //这里如果是MapperFactoryBean对象,初始化完成以后会进入下面的判断
        Object bean = createBean(name, beanDefinition, args);
        //这里如果是MapperFactoryBean对象,初始化完成以后会进入下面的判断
        return (T) getObjectForBeanInstance(bean, name);
    }

    private Object getObjectForBeanInstance(Object beanInstance, String beanName) {
        if (!(beanInstance instanceof FactoryBean)) {
            return beanInstance;
        }
		
        Object object = getCachedObjectForFactoryBean(beanName);

        if (object == null) {
            FactoryBean<?> factoryBean = (FactoryBean<?>) beanInstance;
            //这里如果是MapperFactoryBean对象,初始化完成以后会进入这里的逻辑
            object = getObjectFromFactoryBean(factoryBean, beanName);
        }

        return object;
    }

FactoryBeanRegistrySupport的方法getObjectFromFactoryBean--->doGetObjectFromFactoryBean()--->factory.getObject()方法

    protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName) {
        if (factory.isSingleton()) {
            Object object = this.factoryBeanObjectCache.get(beanName);
            if (object == null) {
                //这里如果是MapperFactoryBean对象,初始化完成以后会进入这里的逻辑
                object = doGetObjectFromFactoryBean(factory, beanName);
                this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
            }
            return (object != NULL_OBJECT ? object : null);
        } else {
            return doGetObjectFromFactoryBean(factory, beanName);
        }
    }
	

    private Object doGetObjectFromFactoryBean(final FactoryBean factory, final String beanName){
        try {
        	//最终调用MapperFactoryBean的getObject方法获取实际的对象
            return factory.getObject();
        } catch (Exception e) {
            throw new BeansException("FactoryBean threw exception on object[" + beanName + "] creation", e);
        }
    }

MapperFactoryBean的getObject()方法

public T getObject() throws Exception {
    return this.getSqlSession().getMapper(this.mapperInterface);
}

走到这里,是不是也跟我之前文章超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?解析的步骤又是一样的了

getMapper方法的大致调用逻辑链是: SqlSession#getMapper() ——> Configuration#getMapper() ——> MapperRegistry#getMapper() ——> MapperProxyFactory#newInstance() ——> Proxy#newProxyInstance()–>MapperProxy#invoke–>MapperMethod#execute

思考联想

  • 在之前的文章中超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?我们通过new SqlSessionFactoryBuilder().build(xml)最终调用委托给Configuration#MapperRegistry#addMappper() 方法进行mapper接口的注册,而在Spring结合mybatis的过程中,我们通过MapperFactoryBean的checkDaoConfig()最终调用委托给Configuration#MapperRegistry#addMappper() 方法进行mapper接口的注册方法实现,本质上是一样的。

  • 在之前的文章中超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?,我们又通过SqlSessionFactory的openSession()新建一个SqlSession,然后通过session#getMapper()最终调用委托给MapperRegistry#getMapper()——> MapperProxyFactory#newInstance() 方法实现代理对象的生成,而在Spring结合mybatis的过程中,我们通过MapperFactoryBean的getObject()调用this.getSqlSession().getMapper(this.mapperInterface)最终也是委托给MapperRegistry#getMapper()——> MapperProxyFactory#newInstance() 方法实现代理对象的生成,本质也是一样的道理。

后续

刚刚上面的例子我们可以发现:每配置一个mapper,都需要写一个对应的MapperFactoryBean,如果mapper多了这样是很繁琐的。

<!-- Mapper接口对应的FactoryBean配置 -->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.example.mapper.UserMapper" />
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

为了解决这个问题,我们可以使用MapperScannerConfigurer,让它扫描特定的包,自动帮我们成批的创建映射器。这样一来,就能大大减少配置的工作量。具体的实现原理我们后面再进行讲解。

<!-- 配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 注入sqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!-- 给出需要扫描Dao接口包 -->
        <property name="basePackage" value="com.joe.dao"/>
    </bean>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

transformer模型结构|李宏毅机器学习21年

来源&#xff1a;https://www.bilibili.com/video/BV1Bb4y1L7FT?p4&vd_sourcef66cebc7ed6819c67fca9b4fa3785d39 文章目录 概述seq2seqtransformerEncoderDecoderAutoregressive&#xff08;AT&#xff09;self-attention与masked-self attentionmodel如何决定输出的长度…

DL Homework 10

习题6-1P 推导RNN反向传播算法BPTT. 习题6-2 推导公式(6.40)和公式(6.41)中的梯度 习题6-3 当使用公式(6.50)作为循环神经网络的状态更新公式时&#xff0c; 分析其可能存在梯度爆炸的原因并给出解决方法&#xff0e; 当然&#xff0c;因为我数学比较菜&#xff0c;我看了好半…

你知道红细胞基因对单细胞分析的影响吗

大家好&#xff0c;今天周日。最近发现有些单细胞测序数据结果不是很好&#xff0c;或许在作者取样的时候&#xff0c;就注定了后续的生信分析不会太成功~ 本次主要发现一个数据集中出现一大群红细胞基因高表达亚群&#xff0c;对后续分析影响还是挺大的。下面先介绍一下为啥单…

dell r720远程网络安装ubuntu20.04(无U盘)

登陆后界面&#xff0c;在主界面上&#xff0c;我们就可以看到各个硬件组件的状态。在快速启动任务栏中&#xff0c;可以对系统电源进行操作&#xff0c;如开机、关机等。安装操作系统&#xff0c;在虚拟控制台预览处点击>启动 按照浏览器出现的提示确定安装控件等&#x…

4-SpringMVC

文章目录 项目源码地址回顾-MVC什么是MVC&#xff1f;MVC各部分组成 回顾-ServletMaven创建Web项目1、创建Maven父工程pom&#xff0c;并导入依赖2、用Maven新建一个Web Module3、代码&#xff1a;HelloServlet.java3、代码-hello.jsp3、代码-web.xml4、配置Tomcat5、浏览器测试…

“快速排序:一种美丽的算法混沌”(1.hoare)

欢迎来到我的博客&#xff01;在今天的文章中&#xff0c;我将采用一种独特且直观的方式来探讨我们的主题&#xff1a;我会使用一幅图像来贯穿整篇文章的讲解。这幅精心设计的图表不仅是我们讨论的核心&#xff0c;也是一个视觉辅助工具&#xff0c;帮助你更深入地理解和掌握本…

GPIO的使用--USART串口通信--传感器控制数据

目录 一、串口通信 1、概念 2、原理图 3、使用步骤 &#xff08;1&#xff09;寻找串口位置 &#xff08;2&#xff09;确定引脚编号 &#xff08;3&#xff09;编写代码 4、实验结果 实验代码 main.c usart.c usart.h 一、串口通信 1、概念 串行接口是一种可以将…

订单系统的设计与海量数据处理实战

概述 订单系统可以说是整个电商系统中最重要的一个子系统&#xff0c;因此订单数据可以算作电商企业最重要的数据资产。订单系统从代码上来说可分为两部分&#xff1a;订单程序和历史订单处理程序。数据存储进行分库分表。 订单系统业务分析 对于一个合格的订单系统&#xf…

程-c1语言-数组------—维数组和二维数组

1. 数组------—维数组和二维数组 字符数组中只能存放字符或字符串&#xff0c;这句话对不对&#xff1f; 字符数组中只能存放字符或字符串&#xff0c;这句话对不对&#xff1f; 不对&#xff0c;字符数组实际上是存放字符编码的 不对 &#xff0c;字符数组实际上是存放字符…

2024 年勒索软件:预期影响、目标和格局变化

随着勒索软件持续增加&#xff0c;我们可以预期这些组织 将继续改进其攻击方式并进行更大规模的操作以获取更大的利润。 如果组织不采取更积极的安全策略&#xff0c;就会面临更高的风险。 以下是我们预计 2024 年勒索软件的情况。 2024 年&#xff0c;我们将看到更多大规模…

解码方法dp

1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 从左往右 5.返回值 dp[n-1] 6.处理边界问题以及初始化问题的技巧

OpenAI承认ChatGPT变懒惰,正在修复该问题

OpenAI旗下的官方ChatGPT账号在社交平台表示&#xff0c;已经收到了大量用户关于GPT-4变懒惰的反馈。 这是因为自11月11日以来&#xff0c;OpenAI就没有更新过该模型。当然这不是故意的&#xff0c;大模型的行为是不可预测的&#xff0c;正在研究修复该问题。 外界猜测&#x…

深入了解UDP协议:特点、应用场景及市面上常见软件案例

目录 引言 UDP的特点 UDP的应用场景 市面上使用UDP的软件案例 结论 引言 在计算机网络中&#xff0c;UDP&#xff08;User Datagram Protocol&#xff09;是一种面向无连接、无状态的传输层协议。与TCP相比&#xff0c;UDP具有独特的特点和适用场景。本文将深入探讨UDP协…

Spring Cloud gateway - CircuitBreaker GatewayFilte

前面学习Spring cloud gateway的时候&#xff0c;做测试的过程中我们发现&#xff0c;Spring Cloud Gateway不需要做多少配置就可以使用Spring Cloud LoadBalance的功能&#xff0c;比如&#xff1a; spring:application:name: spring-gatewaycloud:gateway:routes:- id: path…

python通过selenium获取输入框的文本值爬取编辑框内容

以百度首页的输入框为例,当输入‘你好‘后&#xff0c;html中的value的值会变成‘你好’ from selenium import webdriver web webdriver.Chrome() web.get(http://www.baidu.com) # 初始页面 cc web.find_element_by_xpath(//*[id"kw"]) #定位输入通过复制xpat…

提高问卷填写率的策略与方法

在现代社会的研究中&#xff0c;问卷调研是一种常见的数据收集方式。但是&#xff0c;随着数据的快速传播和竞争激烈的市场环境&#xff0c;怎样吸引大量的人填好问卷成为了科研人员关心的问题。本文将介绍一些方式和策略&#xff0c;以帮助你吸引大量的人填好问卷&#xff0c;…

【C语言】位运算实现二进制数据处理及BCD码转换

文章目录 1&#xff0e;编程实验&#xff1a;按short和unsigned short类型分别对-12345进行左移2位和右移2位操作&#xff0c;并输出结果。2&#xff0e;编程实验&#xff1a;利用位运算实现BCD码与十进制数之间的转换&#xff0c;假设数据类型为unsigned char。3&#xff0e;编…

Cisco Packet Tracer配置命令——交换机篇

交换机VLAN配置 在简单的网络环境中&#xff0c;当交换机配置完端口后&#xff0c;即可直接应用&#xff0c;但若在复杂或规模较大的网络环境中&#xff0c;一般还要进行VLAN的规划&#xff0c;因此在交换机上还需进行 VLAN 的配置。交换机的VLAN配置工作主要有VLAN的建立与删…

JS 云服务 Deno Depoly 宣布,推出定时运行功能 Deno Cron

如果需要定时执行 JS 脚本&#xff0c;以后多一个选项。 Web 构建日益复杂。编写现代软件包括利用云基础设施、剖析模板代码和管理复杂的配置&#xff0c;而开发人员只想专注于编写业务逻辑。 Deno 旨在通过删除配置和不必要的模板&#xff0c;从根本上简化 Web 开发。我们将无…