MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

我們在使用 MyBatis 的時候,都用的是 Dao 接口和 XML 文件裡的 SQL 一一對應來進行使用的。那你是否思考過二者是如何建立關係的?


在開始正文之前,首先解釋 Dao 接口和 XML 文件裡的 SQL 是如何一一對應的?

一句話講完就是:MyBatis 會先解析這些 XML 文件,通過 XML 文件裡面的命名空間 (namespace)跟 DAO 建立關係;然後 XML 中的每段 SQL 會有一個id 跟 DAO 中的接口進行關聯。

那麼問題來了: "如果我有兩個這個 XML 文件都跟這個 DAO 建立關係了,那不是就是衝突了?"

帶著這個疑問我們就要開始下面的正題了!

一、初始化

首先我們要知道每個基於 MyBatis 的應用都是以一個 SqlSessionFactory 的實例為中心的,SqlSessionFactory 的實例可以通過 SqlSessionFactoryBuilder 獲得。

但 SqlSessionFactory 是一個接口,它裡面其實就兩個方法:openSession、getConfiguration

其中,openSession 方法是為了獲取一個 SqlSession 對象,完成必要數據庫增刪改查功能。但是,SqlSessionFactory 屬性太少了,所以需要getConfiguration 的配合;來配置 mapper 映射文件、SQL 參數、返回值類型、緩存等屬性。

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

可以看到 getConfiguration 是屬於 Configuration 類的一個方法。你可以把它當成一個配置管家。MyBatis 所有的配置信息都維持在 Configuration 對象之中,基本每個對象都會持有它的引用。

但日常開發中我們都是將 MyBatis 與 Spring 一起使用的,所以把實例化交給 Spring 處理。

因此我們可以看下 org.MyBatis.spring.SqlSessionFactoryBean,它實現了 InitializingBean 接口。這說明,在這個類被實例化之後會調用到 afterPropertiesSet()。它只有一個方法

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

而這個 afterPropertiesSet 方法只有一個動作,就是buildSqlSessionFactory。它可以分為兩部分來看:

  • 1、從配置文件的 property 屬性中加載各種組件,解析配置到 configuration 中
  • 2、加載 mapper 文件,解析 SQL 語句,封裝成 MappedStatement 對象,配置到 configuration 中。

二、mapper 接口方法是怎樣被調用到的?

大致有如下兩種方式:

  • MyBatis 提供的 API

使用 MyBatis 提供的 API 進行操作,通過獲取 SqlSession 對象,然後根據 Statement Id 和參數來操作數據庫。

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

  • mapper 接口

定義 Mapper 接口,並在裡面定義一系列業務數據操作方法。在 Service 層通過注入 mapper 屬性,調用其方法就可以執行數據庫操作。就像下面這樣

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

那麼,MemberMapper 只是個接口,並沒有任何實現類。我們在調用它的時候,它是怎樣最終執行到我們的 SQL 語句的呢?

三、Mapper 接口的代理創建過程

3.1、首先我們會配置需要掃描的基本包路徑

通過註解的方式配置:

<code>@MapperScan({"com.mmzsblog.business.DAO"})/<code>

或者xml的方式配置:

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

3.2、開始掃描

我們來到 org.MyBatis.spring.mapper.MapperScannerConfigurer 這個類,可以看到它實現了幾個接口。

其中的重點是 BeanDefinitionRegistryPostProcessor。它可以動態的註冊 Bean 信息,方法為 postProcessBeanDefinitionRegistry()。

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

ClassPathMapperScanner 繼承自 Spring 中的類ClassPathBeanDefinitionScanner,所以它的 scan 方法會調用到父類 ClassPathBeanDefinitionScanner 的 scan 方法,

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

而在父類的 scan 方法中又調用到子類 ClassPathMapperScanner 重寫的 doScan方法。

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

此處 super.doScan(basePackages) 是 Spring 中的方法,就不貼代碼多敘述了,想詳細瞭解的話,可以自己翻一下源碼哦。

3.3、bean 註冊完成並創建 sqlSession 代理

並且經過上面這些步驟,此時已經掃描到了所有的 Mapper 接口,並將其註冊為 BeanDefinition 對象。而註冊的時候就是用到了上面 doScan 方法中的 processBeanDefinitions 方法。

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?


處理的過程相對比較簡單,只是往 BeanDefinition 對象中設置了一些屬性。例如:

  • 設置 beanClass

設置 BeanDefinition 對象的 BeanClass 為 MapperFactoryBean> 。這就相當於使用 MemberMapper 註冊時:當前的 mapper 接口在 Spring 容器中,beanName 是 memberMapper,beanClass 是 MapperFactoryBean.class。故在Spring 的 IOC 初始化的時候,實例化的對象就是 MapperFactoryBean 對象。

  • 設置 sqlSessionFactory 屬性

為 BeanDefinition 對象添加屬性 sqlSessionFactory,是為了 BeanDefinition對象設置 PropertyValue 的時候,方便調用到 setSqlSessionFactory()。

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

3.4、創建 sqlSession 代理類

最終在 setSqlSessionFactory 這個方法裡,sqlSession 獲取到的是 SqlSessionTemplate 實例。而在 SqlSessionTemplate 對象中,主要包含sqlSessionFactory 和 sqlSessionProxy,而 sqlSessionProxy 實際上是 SqlSession 接口的代理對象。實際調用的是代理類的 invoke 方法。

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

3.5、小結

Mapper 接口的代理創建過程大致如下:

  • 1、掃描 mapper 接口基本包路徑下的所有對象,將其註冊為BeanDefinition 對象
  • 2、設置 BeanDefinition 的對象的 beanClass 和 sqlSessionFactory 屬性(而其中獲取 BeanDefinition 對象的時候,調用其工廠方法 getObject,返回 mapper 接口的代理類)
  • 3、設置 sqlSessionFactory 屬性的時候,會調用 SqlSessionTemplate 的構造方法,創建 SqlSession 接口的代理類

最後我們在 Service 層,通過

<code>@Resource private MemberMapper memberDao;/<code>

注入屬性的時候,返回的就是代理類。執行 memberDao 的方法的時候,實際調用的也是代理類的 invoke 方法。

四、回答最開始的問題

MyBatis 在初始化 SqlSessionFactoryBean 的時候,找到配置需要掃描的基本包路徑去解析裡面所有的 XML 文件。重點就在如下兩個地方:

1、創建 SqlSource

MyBatis 會把每個 SQL 標籤封裝成 SqlSource 對象。然後根據 SQL 語句的不同,又分為動態 SQL 和靜態 SQL 。其中,靜態 SQL 包含一段 String 類型的 SQL 語句;而動態 SQL 則是由一個個 SqlNode 組成

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

2、創建 MappedStatement

XML 文件中的每一個 SQL 標籤就對應一個 MappedStatement 對象,這裡面有兩個屬性很重要。

  • id

全限定類名+方法名組成的 ID。

  • sqlSource

當前 SQL 標籤對應的 SqlSource 對象。創建完 MappedStatement 對象,會將它緩存到 Configuration#mappedStatements 中。

前面初始化中提到的 Configuration 對象,我們知道它就是 MyBatis 中的配置大管家,基本所有的配置信息都維護在這裡。

例如下面這樣一段代碼:

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

把所有的 XML 都解析完成之後,Configuration 就包含了所有的 SQL 信息。然後解析完成的 XML 大概就是這樣了:

MyBatis 的 DAO 接口跟 XML 文件裡面的 SQL 是如何建立關係的?

看到上面的圖示,聰明如你,也許就大概知道了。當我們執行 MyBatis 方法的時候,就通過全限定類名+方法名找到 MappedStatement 對象,然後解析裡面的 SQL 內容,執行即可。


分享到:


相關文章: