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 {@Beanpublic 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();}}}@Overridepublic 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#getObjectreturn (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>

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/212972.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/212972.shtml
英文地址,請注明出處:http://en.pswp.cn/news/212972.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

lv12 開發板啟動過程

1 開發板啟動過程 1.1 回顧芯片手冊第三章內存映射 對于arm來說&#xff0c;不是給它多大的內存都能讀。尋址空間&#xff08;地址空間&#xff09;讀寫范圍是有限的&#xff0c;尋址空間的大小與地址總線寬度有關&#xff0c;如32位&#xff0c;地址空間4G&#xff08;2^32)…

NVMe over Fabrics with SPDK with iRDMA總結 - 3

6.0 Configure and Test NVMe over Fabrics Host(s) to Connect to SPDK Target配置和測試 NVMe over Fabrics 主機以連接 SPDK 目標機 The SPDK NVMe-oF target system is spec compliant, which allows for the use of either an SPDK host or Linux Kernel host to co…

【C語言基礎】嵌入式面試經典題(C語言篇)----有新的內容會及時補充、更新!

&#x1f4e2;&#xff1a;如果你也對機器人、人工智能感興趣&#xff0c;看來我們志同道合? &#x1f4e2;&#xff1a;不妨瀏覽一下我的博客主頁【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸對你有幫助&#xff0c;可點贊 &#x1f44d;…

Mac虛擬機CrossOver23破解版下載和許可證下載

CrossOver Mac Mac 和 Windows 系統之間的兼容工具。使 Mac 操作系統的用戶可以運行 Windows 系統的應用&#xff0c;從辦公軟件、實用工具、游戲到設計軟件&#xff0c; 您都可以在 Mac 程序和 Windows 程序之間隨意切換。 系統要求 運行macOS的基于Intel或Apple Silicon 的…

springboot項目加載配置文件失敗

問題 在使用springboot打成jar以后&#xff0c;需要文件加載一個redisson-cluster的配置文件。配置文件是在jar的同級目錄。啟動時卻總是加載jar中的配置文件&#xff0c;而外部配置文件卻不加載看下配置&#xff1a;spring:redis:redisson:# redis配置位置file: classpath:red…

lcx iptables rinetd 三個端口轉發流量分析

lcx流量分析 環境搭建 本機 &#xff1a;192.168.0.52 win7 &#xff1a; 192.168.0.247 10.0.0.3 win10&#xff1a; 10.0.0.10 win7 Lcx.exe -listen 7777 4444win10 Lcx.exe -slave 10.0.0.3 7777 127.0.0.1 3389然后使用遠程軟件連接 連的是192.168.0.247的4444 端口 …

基于Pytorch框架深度學的垃圾分類智能識別系統

歡迎大家點贊、收藏、關注、評論啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代碼。 文章目錄 一項目簡介 二、功能三、系統四. 總結 一項目簡介 垃圾分類智能識別系統是一種基于深度學習技術的智能系統&#xff0c;用于對垃圾進行分類和識別。它使用Pytorch框架…

【電路筆記】-壓敏電阻

壓敏電阻 文章目錄 壓敏電阻1、概述2、交流波形瞬變3、抗靜電能力4、特性曲線5、壓敏電阻電容值6、金屬氧化物壓敏電阻7、壓敏電阻應用8、總結 壓敏電阻是一種無源兩端固態半導體器件&#xff0c;用于為電氣和電子電路提供保護。 1、概述 與提供過電流保護的保險絲或斷路器不同…

Redis高效恢復策略:內存快照與AOF

第1章&#xff1a;Redis宕機恢復的重要性和挑戰 大家好&#xff0c;我是小黑。今天咱們來聊聊Redis宕機后的恢復策略。想象一下&#xff0c;你的網站突然宕機了&#xff0c;所有的數據都飄了&#xff0c;這種情況下&#xff0c;快速恢復數據就顯得尤為重要。Redis作為一個高性…

Python---自定義模塊

1、什么是自定義模塊 在Python中&#xff0c;模塊一共可以分為兩大類&#xff1a;內置系統模塊 和 自定義模塊 模塊的本質&#xff1a;在Python中&#xff0c;模塊的本質就是一個Python的獨立文件&#xff08;后綴名.py&#xff09;&#xff0c;里面可以包含全局變量、函數以…

大廠算法指南:優選算法 ——雙指針篇(下)

大廠算法指南&#xff1a;優選算法 ——雙指針篇&#xff08;上&#xff09; 前言&#xff1a;雙指針簡介一、[611. 有效三角形的個數](https://leetcode.cn/problems/valid-triangle-number/)1.1 算法思路&#xff08;排序 雙指針&#xff09;1.2 代碼實現 二、[LCR 179. 查找…

[GPT]Andrej Karpathy微軟Build大會GPT演講(下)--該如何使用GPT助手

該如何使用GPT助手--將GPT助手模型應用于問題 現在我要換個方向,讓我們看看如何最好地將 GPT 助手模型應用于您的問題。 現在我想在一個具體示例的場景里展示。讓我們在這里使用一個具體示例。 假設你正在寫一篇文章或一篇博客文章,你打算在最后寫這句話。 加州的人口是阿拉…

佳明(Garmin) fēnix 7X 增加小睡檢測功能

文章目錄 &#xff08;一&#xff09;零星小睡&#xff08;二&#xff09;小睡檢測&#xff08;三&#xff09;吐槽佳明&#xff08;3.1&#xff09;心率檢測&#xff08;3.2&#xff09;光線感應器&#xff08;3.3&#xff09;手表重量&#xff08;3.4&#xff09;手表續航 &a…

保姆級 | XSS Platform環境搭建

0x00 前言 XSS Platform 平臺主要是用作驗證跨站腳本攻擊。該平臺可以部署在本地或服務器環境中。我們可以使用 XSS Platfrom 平臺搭建、學習或驗證各種類型的 XSS 漏洞。 0x01 環境說明 HECS(云耀云服務器)xss platformUbuntu 22.04Nginx 1.24.0MySQL 5.6.51Pure-Ftpd 1.0.49…

最新接口自動化測試面試題

前言 前面總結了一篇關于接口測試的常規面試題&#xff0c;現在接口自動化測試用的比較多&#xff0c;也是被很多公司看好。那么想做接口自動化測試需要具備哪些能力呢&#xff1f; 也就是面試的過程中&#xff0c;面試官會考哪些問題&#xff0c;知道你是不是真的做過接口自…

大數據面試總結 二

1、事實表主要分成幾種&#xff1a; 1、事務事實表&#xff1a;又稱作原子事實表&#xff0c;主要是用來描述業務過程&#xff0c;跟蹤控件或者時間上某點的度量事件&#xff0c;保存的是最原子的數據 2、周期事實表&#xff1a;以一個周期作為一個時間間隔&#xff0c;用來記…

2021版吳恩達深度學習課程Deeplearning.ai 05序列模型 12.5

學習內容 05.序列模型 1.1 為什么用序列模型 1.序列模型常見的應用 1.2 注釋 notation 1.*T_x(i)表示訓練樣本x(i)的序列長度&#xff0c;T_y(i)表示target(i)的序列長度2.訓練集表示單詞的方式*構建字典的方式*在訓練集中查找出現頻率最高的單詞*網絡搜集常用字典3.如果遇…

【C語言快速學習基礎篇】之一基礎類型、進制轉換、數據位寬

文章目錄 一、基礎類型(根據系統不同占用字節數會有變化)1.1、有符號整形1.2、無符號整形1.3、字符型1.4、浮點型1.5、布爾型 二、進制轉換2.1、二進制2.2、八進制2.3、十進制2.4、十六進制2.5、N進制2.6、進制轉換關系對應表 三、數據位寬3.1、位3.2、字節3.3、字3.4、雙字3.5…

程序員常用英文單詞

英語對于程序員來說多么重要應該無需過多解釋了&#xff0c;把近期在網上收集到的分享到這里供大家學習交流。 PS&#xff1a;感謝原作者的收集&#xff0c;謝謝。 A abstract 抽象的 abstract base class (ABC) 抽象基類abstract class 抽象類 abstraction 抽象、抽象物、抽象…