MyBatis集成到Spring

来源:https://www.cnblogs.com/bigshark/archive/2019/08/19/11374857.html
-Advertisement-
Play Games

使用 MyBatis 的 SqlSession MyBatis 的 提供了執行 SQL 語句、提交或回滾事務和獲取映射器實例的方法。 SqlSession 由工廠類 SqlSessionFactory 來創建, SqlSessionFactory 又是構造器類 SqlSessionFactoryBu ...


使用 MyBatis 的 SqlSession

MyBatis 的 提供了執行 SQL 語句、提交或回滾事務和獲取映射器實例的方法。SqlSession 由工廠類 SqlSessionFactory 來創建,SqlSessionFactory 又是構造器類 SqlSessionFactoryBuilder 創建的。

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

使用 mybatis-spring 的 SqlSession

使用 mybatis-spring 集成 Spring 時 ,SqlSessionFactory 使用了 Spring 的 FactoryBean 的實現類 SqlSessionFactoryBean 間接地調用 SqlSessionFactoryBuilder 來創建。 SqlSession 由 它的線程安全的實現類 SqlSessionTemplate 替代,它能基於 Spring 的事務機制自動提交、回滾、關閉 session。要在 Spring 容器中使用 SqlSessionTemplate,就要將其註入到容器中。

// 註入 SqlSessionTemplate
@Bean
public SqlSessionTemplate sqlSession() throws Exception {
    return new SqlSessionTemplate(sqlSessionFactory());
}

public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    // 指定數據源連接信息
    factoryBean.setDataSource(dataSource());
    // 指定 mapper 文件路徑
    InputStream inputStream = Resources.getResourceAsStream("mapper/UserSpringMapper.xml");
    factoryBean.setMapperLocations(new InputStreamResource(inputStream));
    return factoryBean.getObject();
}

// 使用 Spring 事務機制
@Bean
PlatformTransactionManager getTransactionManager() {
    return new DataSourceTransactionManager(dataSource());
}

使用 mybatis-spring-boot-starter 自動註入

如果使用 Springboot,可以通過引入mybatis-spring-boot-starter,將 MyBatis 的組件自動註入到 Spring 容器中,這個 starter 會引入mybatis-spring-boot-autoconfigure(查看如何開發自己的 Springboot starter),這個包裡面有一個重要的配置類MybatisAutoConfiguration,通過查看其源碼可知,它還有兩個靜態內部類MapperScannerRegistrarNotFoundConfigurationAutoConfiguredMapperScannerRegistrar,其中,MybatisAutoConfigurationMapperScannerRegistrarNotFoundConfiguration都加了 Spring 的 @Configuration 註解,所以 Spring 啟動時會將它們都載入到容器中,而AutoConfiguredMapperScannerRegistrar是通過MapperScannerRegistrarNotFoundConfiguration的註解 @Import 間接地註入容器的。

AutoConfiguredMapperScannerRegistrar實現了 ImportBeanDefinitionRegistrar,所以其方法 registerBeanDefinitions() 會在容器啟動時執行,主要有如下兩個作用:

  1. 從 BeanFactory 獲取包掃描的路徑
  2. 初始化和配置 MapperScannerConfigurer (指定註解類型為 @Mapper、指定包路徑等),註冊到 BeanFactory

MapperScannerConfigurer 實現了 BeanDefinitionRegistryPostProcessor,所以其方法 postProcessBeanDefinitionRegistry() 會在容器啟動時執行,通過這個方法初始化 ClassPathBeanDefinitionScanner 的子類 ClassPathMapperScanner,調用 scan(String... basePackages),掃描包路徑下 @Mapper 註解的所有介面,註冊到 BeanFactory,接著進行後置處理:

  1. 將 BeanDefinition 的類型修改為 MapperFactoryBean
  2. 指定 MapperFactoryBean 的構造器參數為 @Mapper 介面類的全類名
  3. 設置 sqlSessionFactory、sqlSessionTemplate、按照類型自動裝配等
  4. 利用反射創建 MapperFactoryBean 實例,調用其有參構造器,將 @Mapper 介面傳入,緩存到 Class mapperInterface

如下圖: MapperFactoryBean 的繼承關係

MapperFactoryBean

初始化和配置解析

DaoSupport 實現了 InitializingBean.afterPropertiesSet(),通過這個方法,將 Mapper 緩存到 MapperRegistryMap<Class<?>, MapperProxyFactory<?>> knownMappers,key 為 Mapper 介面,value 為 Mapper 代理工廠類 MapperProxyFactory;最後,使用 MapperAnnotationBuilder.parse() 來解析 XML 配置文件或者方法註解,緩存到 ConfigurationMap<String, MappedStatement> mappedStatements,源碼流程如下:

DaoSupport.afterPropertiesSet()
->MapperFactoryBean.checkDaoConfig()
->Configuration.addMapper(this.mapperInterface)
->MapperRegistry.addMapper(type)
->knownMappers.put(type, new MapperProxyFactory<>(type))
// 解析 SQL 配置
->MapperAnnotationBuilder.parse()
-->configuration.addMappedStatement(statement)

生成代理對象

MapperFactoryBean 實現了 FactoryBean.getObject(),從 knownMappers 緩存取出 Mapper 介面映射的 MapperProxyFactory,使用這個工廠類來創建 MapperProxy 代理類,從 MapperProxy<T> implements InvocationHandler 可知是使用了 JDK 的動態代理,源碼流程如下:

MapperFactoryBean.getObject()
->SqlSessionTemplate.getMapper(mapperInterface)
->Configuration.getMapper(mapperInterface, this)
->MapperRegistry.getMapper(mapperInterface, sqlSession)
->MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
->mapperProxyFactory.newInstance(sqlSession)

public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

到這裡,代理對象就生成了,在 Springboot 應用中就可以簡單的通過 @Autowired 的註解方便的從容器中獲取 Mapper 介面的代理對象(MapperProxy)了。

執行流程

假設存在 @Mapper 註解的類 UserDao。

@Mapper
public interface UserDao {
  @Select("select * from t_user where id = #{id}")
  Optional<UserEntity> findOne(String id);
}

通過 @Autowired 獲取 Bean。由上面可知,實際獲取到的是代理對象 MapperProxy。

@Autowired
UserDao userDao;

調用 UserDao 的方法實際上執行的是代理對象 MapperProxy 的 invoke() 方法。

// 調用 findOne
userDao.findOne(id);

// 實際執行的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
}

invoke() 方法大致的源碼執行流程如下:

MapperMethod.execute(sqlSession, args)
sqlSessionProxy.selectOne(statement, parameter)

需要註意 SqlSession在 SqlSessionTemplate 的有參構造器中初始化,並且它也是個代理類,被 SqlSessionInterceptor 代理

this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
    new Class[] { SqlSession.class }, new SqlSessionInterceptor());

所以 selectOne 方法會被 SqlSessionInterceptor.invoke() 攔截,反射執行 SqlSession.selectOne() 方法,源碼流程如下:

private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 打開獲取 DefaultSqlSession;
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    try {
      // 反射執行 SqlSession 的方法 selectOne(String statement, Object parameter) 進行查詢
      Object result = method.invoke(sqlSession, args);
      if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        // 提交
        sqlSession.commit(true);
      }
      // 返回查詢結果
      return result;
    } catch (Throwable t) {
      // 異常時釋放連接
      closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
    } finally {
      if (sqlSession != null) {
        // 釋放連接
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}

註解和配置文件

Springboot 應用同樣可以選擇使用註解,或者配置文件的方式使用 MyBatis,一般簡單的增刪改查直接使用註解的方式(比如 @Select、@SelectProvider)即可,可以減少很多配置文件;比較複雜的 SQL 可能還是使用配置文件的方式操作起來更加方便一些,具體還是得看實際情況來選擇,需要註意的是,每個 DAO 可以同時存在註解和配置的方式,但是同一個方法不能同時存在註解和配置的方式。

如果是通過配置文件的方式,可以在 application.yml 配置文件指定 DAO 的配置文件所在位置:

# 使用基於配置文件的 MyBatis 時指定 Mapper 配置的路徑
mybatis:
  mapper-locations: mapper/*Dao.xml

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

-Advertisement-
Play Games
更多相關文章
  • html代碼: js代碼: ...
  • layer彈出視窗在彈出時指定了area,彈出後,如果當前頁面(iframe)大小比彈出的視窗小,那麼就會出現無法操作彈出視窗的尷尬情況。如圖: 彈出視窗比當前頁面大,這時,唯有放大整個頁面才能看到完全的彈出視窗,才可以操作。 layui 為我們提供了 layer.style(); 方法來重新跳整窗 ...
  • 效果圖 ...
  • 目錄 js面向對象編程 js原型鏈 共用方法 原型繼承 class繼承 js面向對象編程 js原型鏈 共用方法 原型繼承 class繼承 js面向對象編程 js面向對象編程不同於 java 的類和對象 JavaScript 不區分類和實例的概念,而是通過原型(prototype)來實現面向對象編程。 ...
  • 一、引言 單例模式應該算是23種設計模式中比較簡單的,它屬於創建型的設計模式,關註對象的創建。 二、概念 單例模式是23個“Gang Of Four”的設計模式之一,它描述瞭如何解決重覆出現的設計問題,以設計靈活且可復用的面向對象軟體,使對象的實現、更改、測試和重用更方便。 單例模式解決了以下問題: ...
  • 面向對象有三大特性分別是繼承、封裝和多態。 (1)繼承:繼承是一種聯結類的層次模型,並且允許和鼓勵類的重用,它提供了一種明確表述共性的方法。對象的一個新類可以從現有的類中派生,這個過程稱為類繼承。新類繼承了原始類的特性,新類稱為原始類的派生類(子類),而原始類稱為新類的基類(父類)。派生類可以從它的 ...
  • 法一(本地sql查詢,註意表名啥的都用資料庫中的名稱,適用於特定資料庫的查詢) 法二(jpa已經實現的分頁介面,適用於簡單的分頁查詢) 法三(Query註解,hql語局,適用於查詢指定條件的數據) 可以自定義整個實體(Page<User>),也可以查詢某幾個欄位(Page<Object[]>),和原 ...
  • 前言 因為C++是以C為基礎的,所以要用C++編程就必須熟悉C的語法。 C語言的學習可以學習K & R C的《C程式設計語言》 創建函數 Q: 函數原型? A: 標準C/C++有一個特征叫函數原型(function prototyping)。調用函數時,編譯器使用原型確保正確傳遞參數並且正確處理返回 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...