王有志,一個分享硬核Java技術的互金摸魚俠
加入Java人的提桶跑路群:共同富裕的Java人
大家好,我是王有志。在上一篇文章的最後,我們寫了一個簡單的例子,今天我們就透過這個例子來看一看一個標準的 MyBatis 應用程式由哪些元件組成。
最後,文末會解答小夥伴在私信中提出的問題:當存在多個 Mapper.xml,且每個檔案中都有同名的查詢語句時在 MyBatis 應用中該如何進行查詢?
Tips:文章中的示例,指的是《MyBatis 入門》正文中出現的簡單示例和附錄中“不使用 XML 構建 SqlSessionFactory”的例子,如無特別說明,預設為正文中的簡單示例。
MyBatis 應用的組成
我們先來回憶一下構建簡單示例的過程:
- 建立資料物件 UserDO,用於對映資料庫中的 user 表;
- 建立介面 UserDAO,作為 MyBatis 對映器的名稱空間;
- 建立對映器檔案 UserMapper.xml,並編寫了查詢全部 user 表資料的 SQL 語句;
- 建立 MyBatis 的核心配置檔案 mybatis-config.xml,配置了資料庫資訊和對映器。
以上的 4 步是我們在開始使用 MyBatis 前進行的前期配置工作,接下來是我們在應用程式中使用 MyBatis 的步驟:
- 透過 Resources 讀取 mybatis-config.xml 檔案,獲取 Reader 物件;
- 透過 Reader 物件構建出 SqlSessionFactory,即 SQL 會話工廠;
- 透過 SqlSessionFactory 獲取 SqlSession,即 SQL 會話;
- 透過 SqlSession 執行 UserMapper.xml 中的 SQL 語句,並獲取到查詢結果。
這 4 步是我們在應用程式中使用 MyBatis 的過程,綜合以上兩步的內容我們大概可以構建出如下圖所示的 MyBatis 應用的基本組成:
圖中的部分元件已經在我們的示例中出現過了, 但是如 Executor,MappedStatement,Configuration,ResultHandler 等元件並沒有在我們的示例中出現。這是因為它們大都出現在 SqlSession 和 SqlSessionFactory 內部封裝的呼叫過程中,因此我們在平時使用時可能“見不到”它們,但這並不是說它們不重要,相反它們是 MyBatis 中起到關鍵作用的元件。
關於它們我還會在 MyBatis 系列的原始碼篇著重進行分析,不過在此之前,我會按照圖中自下向上的順序,逐一對這些元件的作用做一個簡單的說明。
Tips:Reader 是 Java 中 io 包下的工具類,因此在下文中不會出現關於 Reader 的內容。
Mapper.xml
Mapper.xml 是 MyBatis 的核心之一,是用於定義 SQL 語句和對映規則的 XML 檔案,由核心配置檔案 mybatis-config.xml 載入到 MyBaits 應用程式中。
Mapper.xml 的主要作用包括:
- 定義 SQL 語句:MyBatis 的 SQL 語句編寫在 Mapper.xml 中(MyBatis 也支援透過註解的方式編寫 SQL 語句),透過 MyBatis 提供的 XML 標籤可以實現動態查詢條件和巢狀查詢等複雜的 SQL 語句;
- 對映結果集到 Java 物件:透過 MyBatis 的標籤可以實現資料庫表中的欄位與 Java 物件中的欄位的對映關係,可以實現一對一,一對多等複雜關係的對映;
- 介面方法繫結:Mapper.xml 中定義的 SQL 語句可以透過標籤中的 id 欄位與對應的 Mapper 介面中的方法進行繫結,透過呼叫 Mapper 介面的方法 MyBatis 將會執行 Mapper.xml 中的 SQL 語句。
下面我們對之前的示例稍作修改,來感受下 MyBatis 的中 Mapper.xml 與 Mapper 介面的方法繫結。
首先,在 UserMapper.xml 中定義一個新的查詢語句,用於查詢 id = 1 的使用者:
<select id="selectFirstUser" resultType="com.wyz.entity.UserDO" >
select user_id, name, age, gender, id_type, id_number from user where user_id = 1
</select>
Tips:通常我們不會在 Mapper.xml 中編寫如user_id = 1
這類硬編碼,這裡僅僅是為了舉例說明,千萬不要學~~~
接著我們修改 UserDAO 介面,新增兩個對應的方法宣告:
public interface UserMapper {
List<UserDO> selectAll();
UserDO selectFirstUser();
}
最後我們修改測試程式碼,透過 SqlSession 例項獲取 UseDAO 介面的例項,並呼叫介面中的方法:
@Test
public void test() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserDAO userDAO = sqlSession.getMapper(UserDAO.class);
List<UserDO> users = userDAO.selectAll();
for(UserDO user : users) {
System.out.println(user.getName());
}
UserDO user = userDAO.selectFirstUser();
System.out.println(user.getName());
}
可以看到,這裡我們透過 SqlSession 例項獲取到介面 UserDAO 的例項,分別呼叫了介面中的方法並能夠成功獲取到資料,這表明我們已經將 UserMapper.xml 中編寫的 SQL 語句與 UserDAO 介面中的方法繫結到了一起。
關於 Mapper.xml 的更多用法,我會在 MyBatis 系列的第 4 篇文章中和大家分享。
mybatis-config.xxml
mybatis-config.xml 是 MyBatis 應用中的核心配置檔案,該檔案中包含了 MyBatis 應用程式在執行時所需要的各種配置資訊。
示例中,我只做了最基礎的環境配置(資料庫事務管理器配置,資料來源配置)和對映器配置(載入對映器 UserMapper.xml),但實際上 mybatis-config.xml 中還提供了非常多的配置內容,如:別名配置(使用 typeAliases 標籤),外掛配置(使用 plugins 標籤)和物件工廠配置(使用 objectFactory 標籤)等等。
關於 mybatis-config.xml 的更多用法,我會在 MyBatis 系列的第 3 篇文章中和大家分享。
Resources
MyBatis 提供的資源載入工具,用於各種資原始檔的載入和訪問。Resources 提供了良好的封裝,使用起來非常簡單,只需要透過相對路徑,即可將資原始檔載入到應用程式中。
XMLConfigBuilder
XMLConfigBuilder 繼承自 BaseBuilder,負責解析 MyBatis 中的 XML 配置檔案(mybatis-config.xml),並透過呼叫XMLConfigBuilder#parse
方法構建出 Configuration 物件。
BaseBuilder 有多個子類:
BaseBuilder 的子類分別負責解析不同的檔案,如:XMLConfigBuilder 負責解析 mybatis-config.xml 檔案,XMLMapperBuilder 負責解析 Mapper.xml 檔案等等。
Configuration
Configuration 是核心配置檔案 mybatis-config.xml 在 Java 應用程式中的體現,是 MyBatis 在整個執行週期中的配置資訊管理器,包含了 MyBatis 執行期間所需要的全部配置資訊和對映器。
SqlSessionFactoryBuilder
SqlSessionFactoryBuilder 使用了建造者模式,用來根據配置資訊生成 SqlSessionFactory。SqlSessionFactoryBuilder 提供了多個SqlSessionFactoryBuilder#build
的過載方法,分別接受 Reader,InputStream 和 Configuration 三種方式輸入的配置資訊。
示例中,我們已經在SqlSessionFactoryBuilder#build
方法中使用了 Reader 和 Configuration,而使用 InputStream 的方式與使用 Reader 的方式一模一樣,程式碼如下所示:
@BeforeClass
public static void init() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
}
SqlSessionFactoryBuilder 的唯一作用是建立 SqlSessionFactory,當它完成了這個使命後,我們就應該毫不猶豫的拋棄它,因此 SqlSessionFactoryBuilder 應該作為方法內的區域性變數出現,生命週期僅在這個方法中,就像示例中的那樣。
SqlSessionFactory
SqlSessionFactory 是 MyBatis 中的介面,也是 MyBatis 的核心元件之一,SqlSessionFactory 使用了工廠方法,定義了 MyBatis 獲取 SqlSession 的規範。MyBatis 官方對於 SqlSessionFactory 的定位是每個 MyBatis 應用的核心:
每個基於 MyBatis 的應用都是以一個 SqlSessionFactory 的例項為核心的。
SqlSessionFactory 作為 MyBatis 應用程式中的核心,生命週期與整個 MyBatis 應用程式相同,隨著應用的建立而建立,應用的停止而銷燬。
SqlSessionFactory 有兩個實現類:
DefaultSqlSessionFactory 是 SqlSessionFactory 的預設實現類,用於獲取非執行緒安全的 SqlSession 例項,透過 DefaultSqlSessionFactory 獲取的 SqlSession 例項在使用時還需要手動關閉(同時會提交事務),即呼叫SqlSession#close
方法。
SqlSessionFactory 介面提供了多個SqlSessionFactory#openSession
的過載方法:
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
}
使用無參的SqlSession#openSession
方法可以獲取具有如下特性的 SqlSession 例項:
- 不會自動提交資料庫事務;
- 透過 mybatis-config.xml 配置的資料來源獲取的 Connection 例項;
- 使用資料來源預設的事務隔離級別;
- 不會複用預處理語句,也不會批次進執行更新語句。
那麼對於其它SqlSession#openSession
過載方法中的引數,我們能夠很輕鬆得想到它們的作用:
boolean autoCommit
,設定 SqlSession 是否自動提交;Connection connection
,設定 SqlSession 中使用的 Connection 例項(允許透過其它資料來源獲取);TransactionIsolationLevel level
,設定 SqlSession 中使用的事務隔離級別。
至於 ExecutorType 引數,它是用來選擇 MyBatis 執行器的,MyBatis 中定義了 3 種型別的執行器:
ExecutorType#SIMPLE
,該執行器會為每條 SQL 語句建立新的 PreparedStatement 例項;ExecutorType#REUSE
,該執行器會複用 PreparedStatement 例項;ExecutorType#BATCH
,該執行器會批次執行所有更新語句。
使用哪個SqlSession#openSession
的過載方法,需要我們根據具體的業務場景來進行選擇。
Tips:因為 SqlSessionManager 同時也實現了 SqlSession 介面,而且在使用過程中更多的是作為 SqlSession 的實現而使用,所以我會將 SqlSessionManager 放在 SqlSession 的章節中進行說明。
SqlSession
SqlSession 是 MyBatis 的介面,同樣也是 MyBatis 的核心元件之一,定義了 MyBatis 與資料庫互動的規範,提供了執行 SQL 語句,提交/回滾事務以及獲取對映器(Mapper 介面)例項的方法。
SqlSession 有兩個實現類:
DefaultSqlSession 是 SqlSession 的預設實現類,透過 DefaultSqlSessionFactory 獲取。
DefaultSqlSession 與 SqlSessionManager 的主要區別體現在兩個方面:
- 執行緒安全:
- DefaultSqlSession 不是執行緒安全的 SqlSession 例項(也可以說是透過 DefaultSqlSessionFactory 獲取的 SqlSession 例項不是執行緒安全的);
- SqlSessionManager 提供了執行緒安全的 SqlSession 例項。
- 事務管理:
- DefaultSqlSession 需要手動提交事務,或者在執行
SqlSession#close
方法時自動提交事務; - 透過 SqlSessionManager 執行 SQL 語句時,會自動的進行事務提交。
- DefaultSqlSession 需要手動提交事務,或者在執行
SqlSession 例項的生命週期對應一次資料庫會話,當我們透過 SqlSessionFactory 獲取 SqlSession 例項時是 SqlSession 生命週期的開始,而我們呼叫SqlSession#close
方法後,是 SqlSession 例項的生命週期的結束,這期間的過程通常對應著一項業務操作從開始到結束的過程,因此我們可以認為 SqlSession 例項的生命週期是一次業務操作從開始到結束的時間。
特別提醒,雖然每個 SqlSession 例項都有與之對應的 Connection 例項,且資料庫互動是由 Connection 例項完成的,但由於資料庫連線池的存在,呼叫SqlSession#close
方法後,SqlSession 例項只是將 Connection 例項“歸還”到資料庫連線池中,而不是呼叫Connection#close
來關閉 Connection 例項,因此我們不能將 SqlSession 例項的生命週期與 Connection 例項的生命週期畫上等號。
Tips:透過 SqlSession 執行 SQL 語句是 iBATIS 時代的用法,在當下的環境中,特別是在 MyBatis 與 Spring 整合後,我們通常會選擇透過 SqlSession 例項獲取對映器例項後直接呼叫介面方法,即在文章開頭中解釋對映器介面方法繫結時的使用方式。
Executor
Executor 是 MyBatis 中的介面,同樣是 MyBatis 中的核心元件。Executor 介面定義了 MyBatis 與資料庫互動的規範。不同 Executor 的實現類提供了不同的特性,例如:SimpleExecutor 每次都會建立 PreparedStatement 物件,ReuseExecutor 會複用已經存在的 PreparedStatement 物件,BatchExecutor 用於批次執行 SQL 更新語句,CachingExecutor 提供了查詢結果的快取能力。
MyBatis 中 Executor 的體系結構如下:
關於 Executor 體系的中各實現類的具體作用與功能,我會在 MyBatis 系列的後續文章中繼續和大家分享。
MappedStatement
MappedStatement 中封裝了 Mapper.xml 檔案中對映的 SQL 語句資訊,包括 SQL 語句的 id,SQL 語句,引數對映資訊,結果集對映資訊,以及快取策略等。
StatementHandler
StatementHandler 是 MyBatis 中的介面,負責 MyBatis 中的 SQL 處理,如預編譯,引數設定,SQL 語句執行等。
MyBatis 中 StatementHandler 的體系結構如下:
ResultSetHandler
ResultHandler 是 MyBatis 中的介面,依舊是 MyBatis 中的核心元件。ResultHandler 只有一個實現類 DefaultResultSetHandler,負責將資料庫返回的結果集對映為 Java 物件,需要注意的是 ResultSetHandler 與 ResultHandler 是不同的,ResultSetHandler 負責 MyBatis 內部將結果集對映為 Java 物件,而 ResultHandler 提供了對結果集資料的二次處理能力,允許開發者進行自定義,會在 ResultSetHandler 處理完結果集的對映後呼叫ResultHandler#handlerResult
方法。
問題答疑
上一篇文章中,我們只配置了一個 UserMapper.xml,因此有些小夥伴產生了迷惑,當存在多個 Mapper.xml,且每個檔案中都有同名的查詢語句時在 MyBatis 應用中該如何進行查詢?
一句話概括就是透過 namespace + id 方式來關聯到唯一的 SQL 語句對映上。類似於,當 Java 應用程式中存在多個同名 Java 類時,我們可以透過全限名的方式訪問不同的 Java 類。
我們先隨便建一個表,SQL 語句如下:
create table company (
id int not null primary key,
company_name varchar(50) not null,
company_address varchar(500) not null
);
接著按照上篇文章中的方式分別建立 company 表對應的 CompanyDO,CompanyDAO 和 CompanyMapper.xml,其中 CompanyMapper.xml 需要定義與 UserMapper.xml 中同名的查詢語句,CompanyMapper.xml 內容如下:
<?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.wyz.dao.CompanyDAO">
<select id="selectAll" resultType="com.wyz.entity.CompanyDO" >
select id, company_name, company_address from company
</select>
</mapper>
接著我們修改 mybatis-config.xml 檔案,新增對映檔案 CompanyMapper.xml:
<configuration>
<!-- 省略資料庫配置的部分 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/CompanyMapper.xml"/>
</mappers>
</configuration>
最後我們修改測試程式碼:
@Test
public void testSelectAll() {
SqlSession sqlSession = sqlSessionFactory.openSession();
List<UserDO> users = sqlSession.selectList("com.wyz.dao.UserDAO.selectAll");
for(UserDO userDo:users) {
log.info(userDo.getName());
}
}
這樣我們就可以透過 namespace + id 的方式對映到指定的 SQL 語句了。
好了,今天的內容就到這裡了,如果本文對你有幫助的話,希望多多點贊支援,如果文章中出現任何錯誤,還請批評指正。最後歡迎大家關注分享硬核 Java 技術的金融摸魚俠王有志,我們下次再見!