我們在使用 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 參數、返回值類型、緩存等屬性。
可以看到 getConfiguration 是屬於 Configuration 類的一個方法。你可以把它當成一個配置管家。MyBatis 所有的配置信息都維持在 Configuration 對象之中,基本每個對象都會持有它的引用。
但日常開發中我們都是將 MyBatis 與 Spring 一起使用的,所以把實例化交給 Spring 處理。
因此我們可以看下 org.MyBatis.spring.SqlSessionFactoryBean,它實現了 InitializingBean 接口。這說明,在這個類被實例化之後會調用到 afterPropertiesSet()。它只有一個方法
而這個 afterPropertiesSet 方法只有一個動作,就是buildSqlSessionFactory。它可以分為兩部分來看:
- 1、從配置文件的 property 屬性中加載各種組件,解析配置到 configuration 中
- 2、加載 mapper 文件,解析 SQL 語句,封裝成 MappedStatement 對象,配置到 configuration 中。
二、mapper 接口方法是怎樣被調用到的?
大致有如下兩種方式:
- MyBatis 提供的 API
使用 MyBatis 提供的 API 進行操作,通過獲取 SqlSession 對象,然後根據 Statement Id 和參數來操作數據庫。
- mapper 接口
定義 Mapper 接口,並在裡面定義一系列業務數據操作方法。在 Service 層通過注入 mapper 屬性,調用其方法就可以執行數據庫操作。就像下面這樣
那麼,MemberMapper 只是個接口,並沒有任何實現類。我們在調用它的時候,它是怎樣最終執行到我們的 SQL 語句的呢?
三、Mapper 接口的代理創建過程
3.1、首先我們會配置需要掃描的基本包路徑
通過註解的方式配置:
<code>@MapperScan({"com.mmzsblog.business.DAO"})/<code>
或者xml的方式配置:
3.2、開始掃描
我們來到 org.MyBatis.spring.mapper.MapperScannerConfigurer 這個類,可以看到它實現了幾個接口。
其中的重點是 BeanDefinitionRegistryPostProcessor。它可以動態的註冊 Bean 信息,方法為 postProcessBeanDefinitionRegistry()。
ClassPathMapperScanner 繼承自 Spring 中的類ClassPathBeanDefinitionScanner,所以它的 scan 方法會調用到父類 ClassPathBeanDefinitionScanner 的 scan 方法,
而在父類的 scan 方法中又調用到子類 ClassPathMapperScanner 重寫的 doScan方法。
此處 super.doScan(basePackages) 是 Spring 中的方法,就不貼代碼多敘述了,想詳細瞭解的話,可以自己翻一下源碼哦。
3.3、bean 註冊完成並創建 sqlSession 代理
並且經過上面這些步驟,此時已經掃描到了所有的 Mapper 接口,並將其註冊為 BeanDefinition 對象。而註冊的時候就是用到了上面 doScan 方法中的 processBeanDefinitions 方法。
處理的過程相對比較簡單,只是往 BeanDefinition 對象中設置了一些屬性。例如:
- 設置 beanClass
設置 BeanDefinition 對象的 BeanClass 為 MapperFactoryBean> 。這就相當於使用 MemberMapper 註冊時:當前的 mapper 接口在 Spring 容器中,beanName 是 memberMapper,beanClass 是 MapperFactoryBean.class。故在Spring 的 IOC 初始化的時候,實例化的對象就是 MapperFactoryBean 對象。
- 設置 sqlSessionFactory 屬性
為 BeanDefinition 對象添加屬性 sqlSessionFactory,是為了 BeanDefinition對象設置 PropertyValue 的時候,方便調用到 setSqlSessionFactory()。
3.4、創建 sqlSession 代理類
最終在 setSqlSessionFactory 這個方法裡,sqlSession 獲取到的是 SqlSessionTemplate 實例。而在 SqlSessionTemplate 對象中,主要包含sqlSessionFactory 和 sqlSessionProxy,而 sqlSessionProxy 實際上是 SqlSession 接口的代理對象。實際調用的是代理類的 invoke 方法。
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 組成
2、創建 MappedStatement
XML 文件中的每一個 SQL 標籤就對應一個 MappedStatement 對象,這裡面有兩個屬性很重要。
- id
全限定類名+方法名組成的 ID。
- sqlSource
當前 SQL 標籤對應的 SqlSource 對象。創建完 MappedStatement 對象,會將它緩存到 Configuration#mappedStatements 中。
前面初始化中提到的 Configuration 對象,我們知道它就是 MyBatis 中的配置大管家,基本所有的配置信息都維護在這裡。
例如下面這樣一段代碼:
把所有的 XML 都解析完成之後,Configuration 就包含了所有的 SQL 信息。然後解析完成的 XML 大概就是這樣了:
看到上面的圖示,聰明如你,也許就大概知道了。當我們執行 MyBatis 方法的時候,就通過全限定類名+方法名找到 MappedStatement 對象,然後解析裡面的 SQL 內容,執行即可。
閱讀更多 java互聯網架構 的文章