Mybatisi和Spring整合源碼分析

来源:https://www.cnblogs.com/yuanbeier/archive/2022/06/19/16391826.html
-Advertisement-
Play Games

一、MybatisSpring的使用 1.創建 Maven 工程。 2.添加依賴,代碼如下 <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7-ybe</version ...


一、MybatisSpring的使用

1.創建 Maven 工程。

2.添加依賴,代碼如下

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.7-ybe</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.6-ybe</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.20</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.16</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.3.16</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.8</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.20.RELEASE</version>
    </dependency>

3.添加實體如下,

package com.ybe.entity;

import java.io.Serializable;

public class Book implements Serializable {
    int id;
    double price;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

4.添加 Mapper介面以及BookMapper.xml文件,

public interface BookMapper {

     Book getBook(@Param("id") int id);
}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.ybe.mapper.BookMapper">
    <cache></cache>
    <select id="getBook" resultType="com.ybe.entity.Book">
        select * from book where id = #{id}
    </select>
</mapper>

5.添加 BookService 和 BookServiceImpl代碼如下,

package com.ybe.service;

import com.ybe.entity.Book;

public interface BookSerivce {
    Book getBook(int id);
}

package com.ybe.service.impl;

import com.ybe.entity.Book;
import com.ybe.mapper.BookMapper;
import com.ybe.service.BookSerivce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class BookServiceImpl implements BookSerivce {
    @Autowired
    BookMapper bookMapper;
    public Book getBook(int id) {
        return bookMapper.getBook(id);
    }
}

6.添加配置類,代碼如下

package com.ybe.config;

@Configuration
@MapperScan(basePackages = {"com.ybe.mapper"})
@ComponentScan(basePackages = {"com.ybe"})
public class MyBatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setConfigLocation(new ClassPathResource("mybatis.xml"));
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
        .getResources("classpath:com/ybe/mapper/*.xml"));
        factoryBean.setTypeAliases(Book.class);
        return factoryBean;
    }

    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("xxx");
        dataSource.setPassword("xxx");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/aopTest?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8");
        return dataSource;
    }

    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

}

7.添加主類代碼,代碼如下

AnnotationConfigApplicationContext configApplicationContext = new AnnotationConfigApplicationContext(MyBatisConfig.class);
BookSerivce bookSerivce = configApplicationContext.getBean(BookSerivce.class);
Book book = bookSerivce.getBook(1);
System.out.println(book.getId());

二、Mybatis和Spring的整合

​ 因為Mybatis中使用的是Mapper.class 介面來找到資料庫sql語句,並且是通過SqlSessionFactory的SqlSession來連接資料庫和執行Sql語句的。所以Mybatis和Spring的整合,其實就是把Mybatis的SqlSessionFactory類和Mapper.Class介面註入到SpringIOC中。並且SqlSessionFactory類中的事務管理對象(SpringManagedTransactionFactory )會集成Spring的事務。

​ 整個整合的過程分為兩部分,第一部分 Mapper介面註入;第二部分 SqlSessionFactoryBean 註入。

2.1 Mapper 介面註入

​ 試想一下我們在寫Mapper介面的時候並沒有寫實現類,只是寫了Mapper.xml文件。那在註入到Spring容器中,具體的實現類是啥?我這裡直接給答案,Mapper介面在Spring容器中對應的實現類是一個MapperFactoryBean的類,最終存在beanFactory.singletonObjects中。MapperFactoryBean實現了FactoryBean介面,在獲取Mapper介面的時候,預設返回的是MapperFactoryBean的getObject()方法,其中具體返回xxxMapper介面的動態代理類(JDK代理方式)。動態代理類會進行緩存。

原理

​ 通過配置@MapperScan(basePackages = {"com.ybe.mapper"}) 註解,向 BeanDefinitionRegistry 中添加類型為 MapperScannerConfigurer 的BeanDefinition對象並且初始化對象相關屬性,在org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors 中會進行調用MapperScannerConfigurer 的postProcessBeanDefinitionRegistry 方法,該方法會掃描配置的包路徑下的Mapper介面class文件,生成BeanClass為MapperFactoryBean的ScannedGenericBeanDefinition,註冊到 BeanDefinitionRegistry 中。

源碼解析
  1. @MapperScan註解源碼如下
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
  1. 在 org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors 方法中,會先執行 org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions的方法,其中會執行MapperScannerRegistrar類的registerBeanDefinitions方法,向 BeanDefinitionRegistry 註冊了一個類型為 MapperScannerConfigurer的BeanDefinition對象。代碼如下,
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
  1. MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor介面。MapperScannerConfigurer主要用來掃描具體的 Mapper介面class文件,生成BeanClass類型為MapperFactoryBean的ScannedGenericBeanDefinition對象後註入 BeanDefinitionRegistry 中。具體類圖如下

  2. 在org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors中會接著執行,MapperScannerConfigurer的postProcessBeanDefinitionRegistry 方法。postProcessBeanDefinitionRegistry中大概邏輯為

搜索指定包下麵的Mapper介面,

// 開始搜索 basePackage
scanner.scan(
    StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

生成ScannedGenericBeanDefinition,並以Mapper介面名稱為BeanName註入到BeanDefinitionRegistry對象中。

BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
    AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);

然後再設置ScannedGenericBeanDefinition的BeanClass類型為MapperFactoryBean類。 關鍵代碼代碼如下

// 設置 definition 的  構造函數的參數值類型為 beanClassName,
// 在創建 MapperFactoryBean 時,會根據beanClassName創建類,然後把類作為參數調用MapperFactoryBean帶參數的構造方法
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
// 設置 definition 的  Bean 類型為 MapperFactoryBean.class
definition.setBeanClass(this.mapperFactoryBeanClass);
  1. 每個Mapper介面文件對應的BeanDefinition為 ScannedGenericBeanDefinition,BeanDefinition的BeanClass實現類為 MapperFactoryBean 類。此時查看beanFactory的 beanDefinitionMap 中的值,如下圖

  2. MapperFactoryBean是一個泛型類,泛型用來表示不同介面類型。繼承了SqlSessionDaoSupport類,該類中存儲了Maybatis的SqlSession工廠類。MapperFactoryBean也是一個實現了FactoryBean的類,用來返回具體的類型以及根據類型來生成具體的Mapper介面代理類。

@Override
public T getObject() throws Exception {
    // 返回根據介面類型返回 SqlSession中的Mapper代理對象
    return getSqlSession().getMapper(this.mapperInterface);
}

/**
   * {@inheritDoc}
   */
@Override
public Class<T> getObjectType() {
    return this.mapperInterface;
}
  1. 在beanFactory.preInstantiateSingletons()方法中會把BeanDefinition生成具體的Bean對象,在創建 MapperFactoryBean 對象的時候會調用帶參數的構造方法(上面有具體說明)。因為在配置類中我們註入了SqlSessionFactoryBean對象(具體解析過程在下麵章節講解),SqlSessionFactoryBean對象實現了FactoryBean介面,在Srping容器中會返回SqlSessionFactory類型對象。在給MapperFactoryBean屬性賦值的時候會把SqlSessionFactoryBean的實際SqlSessionFactory對象賦值給sqlSessionTemplate屬性。最終會在beanFactory.singletonObjects對象中添加以Mapper介面名稱為key,以 MapperFactoryBean 類型為value的 記錄。

  2. 在獲取Mapper介面的Bean對象的時候,會調用getObject()方法,其中會調SqlSessionTemplate的getMapper(),代碼如下

@Override
public <T> T getMapper(Class<T> type) {
  return getConfiguration().getMapper(type, this);
}
  1. 以上調用Configuration的getMapper()方法,configuration就是Mybatis的類了。代碼如下,
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 根據Mapper介面類型獲取已經註冊的對象
    return mapperRegistry.getMapper(type, sqlSession);
}
  1. 返回創建的動態代理對象,這裡返回的動態代理對象之後不會更新beanFactory.singletonObjects的對象,並且會進行緩存處理。關鍵代碼如下
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  // 根據 Mapper 介面類型獲取已經註冊的對象
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    // 創建動態代理對象
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}
  1. 總結一下,beanFactory.singletonObjects關於Mapper介面存儲的Bean對象是泛型的MapperFactoryBean對象。但是根據Mapper介面獲取Bean對象會返回上面的動態代理對象,並且動態代理對象會緩存在beanFactory.factoryBeanObjectCache中,這是因為MapperFactoryBean實現的FactoryBean介面。

2.1 SqlSessionFactoryBean 註入

SqlSessionFactoryBean介紹

通過@Bean方式可以將SqlSessionFactoryBean對象註入到Spring容器,SqlSessionFactoryBean對象在MapperFactoryBean對象中會用到。註入代碼如下,

@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
    // 創建 SqlSessionFactoryBean 的類
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    // 設置數據源
    factoryBean.setDataSource(dataSource);
    // 設置配置文件路徑
    factoryBean.setConfigLocation(new ClassPathResource("mybatis.xml"));
    // 設置 mybaits.xml 文件
    factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                                   .getResources("classpath:com/ybe/mapper/*.xml"));
    // 設置別名
    factoryBean.setTypeAliases(Book.class);
    return factoryBean;
}

SqlSessionFactoryBean的類繼承關係圖如下,

SqlSessionFactoryBean實現了InitializingBean介面 ,會在初始化後會執行afterPropertiesSet方法,其中會調用buildSqlSessionFactory()方法進行SqlSessionFactory的創建。SqlSessionFactoryBean也實現了FactoryBean介面,在容器中如果獲取SqlSessionFactoryBean對象會返回SqlSessionFactory對象,並且也會在beanFactory.factoryBeanObjectCache進行緩存。getObject源碼如下,

@Override
public SqlSessionFactory getObject() throws Exception {
  if (this.sqlSessionFactory == null) {
    afterPropertiesSet();
  }

  return this.sqlSessionFactory;
}
buildSqlSessionFactory()具體邏輯

​ 1. 根據 configLocation 創建 XMLConfigBuilder 以及 Configuration對象

xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();

​ 2. 讀取SqlSessionFactoryBean的屬性對象給 targetConfiguration 賦值

Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

if (hasLength(this.typeAliasesPackage)) {
    scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
        .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
        .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
......

​ 3. 解析主配置文件

xmlConfigBuilder.parse();

​ 4. targetConfiguration設置環境變數,如果配置的transactionFactory 事務工廠類為 null,則創建 SpringManagedTransactionFactory 事務工廠類,該事務工廠會直接調用org.springframework.jdbc.datasource.DataSourceUtils去獲取資料庫連接對象,所以和Spring的事務進行了集成。代碼如下,

// 設置環境變數,如果事務工程類為 null,則創建 SpringManagedTransactionFactory 事務工廠類
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));

​ 5. 如果 mapperLocations 不為null ,則迴圈遍歷 xxxMapper.xml 文件流,解析之後給 targetConfiguration 的相關對象賦值。代碼如下,

for (Resource mapperLocation : this.mapperLocations) {
    if (mapperLocation == null) {
        continue;
    }
    try {
        // 構建 XMLMapperBuilder 對象,進行mapper.xml 文件資源解析
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                                                                 targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
        xmlMapperBuilder.parse();
    } catch (Exception e) {
        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
    } finally {
        ErrorContext.instance().reset();
    }
    LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
  1. 總結一下,整個過程比較簡單,其主要就是Mybaits初始化的過程,更詳細Mybatis初始化過程請參考上一篇文章。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 6月15日,由中國信通院主辦的以 “原生聚力,雲數賦能”為主題的“2022雲原生產業大會”在北京舉行。憑藉創新技術和領先實踐,騰訊云云巢榮獲“雲原生技術創新案例”獎。 騰訊云云巢是騰訊雲自主研發的一站式雲原生有狀態服務平臺,基於Kubernetes容器化架構,為各類有狀態服務提供統一的集群管理、資源 ...
  • 一、開發背景 產品出設計稿要求做一個仿原生app簡訊驗證碼組件,花了兩小時搞出來一個還可以的組件,支持屏幕自適應,可以用於彈出框,或自己封裝的vue組件里,希望可以幫助那些被產品壓榨的同學,哈哈。😄 其核心思想就是利用一個輸入框使用css3,translate屬性,每輸入一次後向右位移一個單位位置 ...
  • 認識WEB **「網頁」**主要是由文字、圖像和超鏈接等元素構成,當然除了這些元素,網頁中還可以包括音頻、視頻以及Flash等。 **「瀏覽器」**是網頁顯示、運行的平臺。 「瀏覽器內核」(排版引擎、解釋引擎、渲染引擎) 常見的瀏覽器及其內核 瀏覽器 內核 備註 IE Trident IE、獵豹安全 ...
  • HTML各知識點總結: 基本標簽 標題標簽、段落標簽、換行標簽、水平線標簽、字體樣式標簽、註釋和特殊符號 網頁插入 圖像、超鏈接,視頻、音頻、列表、表格、表單、內聯框架等 超鏈接 錨鏈接、功能性鏈接 列表 有序列表、無序列表、自定義列表 表格 行、列、跨行、跨列 表單 提交格式、文本框、密碼框、單選 ...
  • 昨天太晚就沒來得及更新,今天是spu管理界面,這個界面一共有三個界面需要切換,完成了兩個界面,而且今天的難度在於最後兩個章節,富有一定的邏輯性,當然中間也有很多需要註意的,比如ElementUI的照片牆需要添加list屬性而且值為你的數據並且必須是一個數組必須有name、url屬性 一.spu管理 ...
  • 本章是系列文章的第七章,終於來到了鼎鼎大名的SSA,SSA是編譯器領域最偉大的發明之一,也是影響最廣的發明。 本文中的所有內容來自學習DCC888的學習筆記或者自己理解的整理,如需轉載請註明出處。周榮華@燧原科技 7.1 控制流圖回顧 對下麵的c代碼保存成7.1.cc: 1 int max(int ...
  • 樣才能拿到大廠的offer,沒有掌握絕對的技術,那麼就要不斷的學習 他是如何拿下阿裡等大廠的offer的呢,今天分享他的秘密武器,美團資深架構師整理的Java核心知識點,面試時面試官必問的知識點,篇章包括了很多知識點,其中包括了有基礎知識、Java集合、JVM、多線程併發、spring原理、微服務、 ...
  • 引言:今天看MicrosoftDoc關於CFileDialog的doModal函數返回值的部分,提到了實際上MFC提供了錯誤信息顯示。 個人技術博客(文章整理+源碼): https://zobolblog.github.io/LearnWinAPI/ 1.用法: CFileDialog::DoMod ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...