Mybatis系列全解(七):全息視角看Dao層兩種實現方式之傳統方式與代理方式

 

封面:洛小汐
做者:潘潘java

Mybatis系列全解封面

一直以來sql

他們都說爲了生活數據庫

便追求所謂成功apache

頂級薪水、名牌包包緩存

還有學區房微信

·session

不過架構

總有人丟了生活app

仍一無所得框架

·

我比較隨遇而安

有些事懶得明白

平日裏心安理得

感興趣的事能一直作

便很滿足

·

難道不是

除了活着

其餘都只是錦上添花嗎

前言

上節咱們介紹了 《 Mybatis系列全解(六):Mybatis最硬核的API你知道幾個? 》一文,詳細解讀了 Mybatis 框架核心設計和 API ,圖文並茂,乾貨滿滿,感興趣的朋友能夠往下翻目錄找到文章的連接傳送門進行閱讀,文章發佈以後被不少網站推薦閱讀,以至於持續至今依然會收到讀者朋友們的點贊評論關注、還有催更,閱讀量日日攀升,固然我甚是開心,一來是兩週梳理的成果能獲得認同,二來也是發覺堅持作本身喜歡的事還能給你們帶來一些知識體驗,總之很欣慰。

在這裏插入圖片描述

回到本篇文章計劃講解內容,咱們仍是繼續沿用以往的文章風格,對 Mybatis 框架在實際開發應用過程當中,Dao 層的實現原理和方式進行解讀,開篇也簡單從 Mybatis 執行 SQL 語句的流程切入,引出咱們研究的內容,再與你們一同以全息視角知其然並知其因此然,下面咱們一塊兒探索吧。

號外: 咱們的 Mybatis 全解系列一直在更新哦

Mybatis 全解系列腦圖全覽

Mybaits系列全解 ( 傳送門 )


本文目錄


一、Mybatis 是如何找到 SQL 語句的 ?

二、爲何有 Dao 層 ?

三、Dao 層的兩種實現方式:傳統與代理

一、Mybatis 是如何找到 SQL 語句的 ?

經過前面的學習,咱們已經對 Mybatis 的架構設計以及核心數據層執行流程都很是瞭解,其實對於咱們應用層的研發用戶來講,使用 Mybatis 框架的目的很簡單,就是但願經過它來消除原有 JDBC 的冗餘代碼邏輯、減輕咱們開發工做量、提高研發效率、以便於咱們可以專一於 SQL 的編寫。因此說到底,是咱們寫 SQL,Mybatis 幫咱們執行 SQL ,跟數據庫作交互,更簡單來講,咱們和 Mybatis 的配合就5步:

一、咱們編寫 SQL

二、發號施令(調用API)

三、Mybatis 找 SQL

四、Mybatis 執行 SQL

五、返回執行結果

看吧,Mybatis 實實在在是數據庫交互的好幫手呢,乖巧又高效,咱們只需編寫好 SQL ,在程序應用中就能夠隨處發號施令(調用API),讓 Mybatis 幫咱們具體執行 SQL。但其實咱們知道 Mybatis 默默作了許多事情,咱們前面也都詳細剖析過的:

例如第1步編寫 SQL,其實 Mybatis 就要求咱們必須提早完成信息配置 Config.xml 與 映射文件 Mapper.xml (後面註解道理相同)再開始編寫 SQL;

例如第2步發號施令,其實就是咱們實際應用當中調用增刪改查接口( 比如sqlsession.selectList );

例如第4步執行 SQL,其實就是會話調用執行器,執行器調用語句處理器,語句處理器結合參數處理器與類型處理器最終底層經過 JDBC 與數據庫交互;

例如第5步返回執行結果,是 JDBC 返回的結果集並映射封裝,最終返回預期的封裝對象。

細心的你可能會發現,咱們第3步沒說到,那第3步是作什麼的呢:Mybatis 找 SQL

到此,開始咱們本小結的研究主題:

Mybatis 是如何找到 SQL 語句的?

針對這個問題,咱們首先細細回想,平日裏咱們的 SQL 語句都編寫在哪些地方呢?嗯 ~ 不出意外的話,我相信你們腦海裏都會浮現兩個地方:一個是 XML 配置文件,另外一個是 Java 註解

沒錯!假如使用 XML 配置方式則在 UserMapper.xml 配置文件中編寫 SQL 語句:

<mapper namespace="com.panshenlian.dao.UserDao">

    <!-- 查詢用戶列表 -->
    <select id="findAll" resultType="com.panshenlian.pojo.User" >
        select * from User 
    </select>
    
</mapper>

使用 XML 配置方式編寫 SQL,會把 XML 中的「 命名空間標識 + 語句塊 ID 」做爲惟一的語句標識,這裏的惟一語句標識爲:

com.panshenlian.dao.UserDao.findAll

假如使用 Java 註解方式則在 UserDao 接口中編寫 SQL 語句:

public class UserDao {
   
    
    /** * 查詢用戶列表 * @return */
    @Select(value =" select * from User ")
    List<User> findAll();
    
}

使用 Java 註解方式編寫 SQL,會把接口中的「 接口全限定名 + 方法名 」做爲惟一的語句標識,這裏的惟一語句標識也是同樣:

com.panshenlian.dao.UserDao.findAll

其實,咱們的 Mybatis 是支持使用 XML 配置方式和 Java 註解兩種方式來編寫 SQL 語句的,二者沒有絕對的孰優孰劣,每一個項目團隊均可以根據自身研發人員編碼習慣/能力、工程的耦合性要求、研發效能性價比等多方面綜合考慮以後去作選擇。畢竟不管咱們使用哪一種方式,目的都只是爲了把實際須要執行的 SQL 準備好,供 Mybatis 跟數據庫交互時使用。

SQL語句集合池

是這樣的,Mybatis 在啓動構建之初,會掃描咱們編寫的 SQL 文件。假如你使用 XML 配置方式編寫 SQL,那麼須要在 Config.xml 核心配置文件中指定映射器文件 mapper.xml (下面代碼演示第一種);若是你使用 Java 註解方式編寫 SQL ,那麼須要在 Config.xml 核心配置文件中也指定加載使用了註解的Mapper接口(下面代碼演示第二種)。

<!-- 第一種:XML配置方式:指定映射器文件 -->
<mappers>
    <mapper resource="UserMapper.xml" />
</mappers>

<!-- 第二種:Java註解方式:指定映射器接口 -->
<mappers> 
    <mapper class="com.panshenlian.dao.UserDao"/>  
</mappers>

一樣不管你使用哪種方式告訴 Mybatis 來掃描/構建,最終都會被統一加載到一個 SQL 語句集合的大池子裏面,它是一個 Map 集合,以咱們上面說的 惟一語句標識 做爲集合的 key,以每一條 SQL 語句對象做爲 value ,而且最終這個 SQL 語句 Map 集合的大池子,會做爲一個屬性設置在全局配置 Configuration 上面,供咱們 Mybatis 在整個應用週期裏頭隨時使用。

看看,每個 SQL 語句都實例成一個 MappedStatement 語句對象,而且這個 SQL 語句 Map 集合的大池子,會做爲全局配置 Configuration 的屬性 mappedStatements 。

// Mybatis 全局配置對象
public class Configuration{
   
    
    // 存儲SQL語句的集合池 
    Map<String, MappedStatement> mappedStatements 
        = new StrictMap<MappedStatement>
}

基本簡單的 SQL 語句解析過程:

SQL語句兌現解析過程

到這裏,我相信有部分好奇的朋友仍是想知道,那 Mybatis 是如何把咱們編寫的每一條 SQL 語句加載到語句集合大池子的呢?又是怎麼保證每條語句在集合大池子中的 Key 值(惟一語句標識)是惟一不會重複的呢?

好奇的小腦殼

嗯,咱們抱着好奇的小腦殼,對這兩個疑問進行探索:

一、Mybatis 是如何把咱們編寫的每一條 SQL 語句加載到語句集合大池子的呢?

首先,咱們看看 Mybatis 在初始構建會話時,會經過加載核心配置文件,得到會話工廠對象:

//加載核心配置文件
InputStream is = 
    Resources.getResourceAsStream("SqlMapConfig.xml");

// 得到sqlSession工廠對象
SqlSessionFactory f = 
    new SqlSessionFactoryBuilder().build(is);

咱們跟蹤了源代碼,發現會話工廠構建器 SqlSessionFactoryBuilder 的build() 邏輯中,在實現會話工廠實例構建的同時,會解析配置文件並封裝成全局配置對象 Configuration 和語句對象集合 MappedStatement 。

Mybatis的SQL語句是怎麼構建成對象的

用異曲同工,來形容 XML 配置方式和 Java 註解方式編寫 SQL 並構建語句集合的過程再好不過了。

二、Mybatis 是怎麼保證每條語句在集合大池子中的 Key 值(惟一語句標識)是惟一不會重複的呢??

根據第1個問題的分析結果,咱們知道 SQL 語句最終會被存放在語句集合中,那這個語句集合是普通 Map 嗎?顯示不是,這個集合實例實際上是 Mybatis 框架在 Configuration 全局配置對象中的一個靜態的匿名內部類 StrictMap,它繼承 HashMap ,重寫了 put() 方法,在 put() 中實現對 Key 值(惟一語句標識)的重複校驗。

// 全局配置
public class Configuration {
   
    
    // 靜態匿名內部類
    protected static class 
        StrictMap<V> extends HashMap<String, V> {
   
        
        // 重寫了put方法
        @Override 
    	public V put(String key, V value) {
   
            
          // 若是出現重複key則拋出異常
          if (containsKey(key)) {
   
            throw 重複異常;
          } 
    	} 
    }
}

因此,不管是使用 XML 配置方式仍是 Java 註解方式,都必須保證每條 SQL 語句有一個 惟一的語句標識,不然在 Mybatis 啓動構建階段,就會收到來自 Mybatis 的解析異常,例如我在 UserMapper.xml 中設置兩個 findAll 語句。

<select id="findAll">
    select 1 from User
</select>

<select id="findAll">
    select * from User
</select>

不出意外,出現 Mybatis 解析 SQL 的異常警告:

// 異常定位很準確 --> 解析 mapper sql 時
### org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration.

// 哪一個 mapper 文件呢 --> UserMapper.xml
### Cause: org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'UserMapper.xml'

// 哪一個 id 重複了呢 --> findAll
### Cause: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.panshenlian.dao.IUserDao.findAll. please check UserMapper.xml and UserMapper.xml

好,到這裏咱們基本清晰,SQL 怎麼存,而且怎麼不重複的存,並且存在哪?那剩下的就很簡單,對於一個 Map 集合的取值,我相信你們都知道,無非就是經過 key 來取到存儲的 value 值。而 Mybatis 中這個語句集合的取值方式也是同樣經過 key 值來去,這個 key 呢,咱們這裏是每一條語句的 惟一語句標識 ,當咱們調用會話 SqlSession 的增刪改查 API 的時候,就會傳遞這個惟一語句標識,告訴 Mybatis :「 幫咱們把這個 key 對應的語句對象的 SQL 執行一下吧 「 ,僅此而已。

先賣個關子

只不過,這裏面當咱們應用層的用戶調用增刪改查 API 的時候,咱們究竟是 **如何把 Key 值告知給 Mybatis 呢?**是 直接 告訴 Mybatis 呢?仍是委婉的(經過代理方式)告訴 Mybatis 。

代理方式

這個就比較有意思了,也是咱們第3部分主題要講解的內容,咱們下面會細說,先看第2部分主題吧~

二、爲何有 Dao 層 ?

在軟件開發中,爲了方便應用程序的研發與維護,通常咱們都會使用清晰合理的框架模式來規範開發行爲,提升同模塊內聚性,減低異模塊耦合性,例如 MVC、MVP、MVVM 等,而其中 MVC(Model-View-Controller) 則是 Java 語言中應用最普遍的分層框架模式。

MVC框架模式

對於 MVC 分層模式,其實最先的設計來源於桌面應用程序,通常 M 表明業務模型 Model,V 表明視圖界面 view,C 表明控制器 Controller ,通常的:

View (視圖層):視圖層直接面向用戶/終端,提供給用戶/終端的指令輸入或界面操做請求。

Controller (控制層):控制層負責接收 「視圖層」 的指令或操做請求,並轉移分派至 「模型層」,接收到 「模型層」 的返回結果以後,會同步傳送回 「視圖層」,達到控制/紐帶的做用。

Model (模型層):模型層是核心的數據信息處理層,分爲業務邏輯處理與數據持久化處理,模型層接收來自 「控制層」 的請求,並經過邏輯運算與數據轉換,再把處理結果返回到 「控制層」。

MVC框架模式模型/視圖轉移過程

從程序編碼角度看,咱們使用 MVC 的主要目的就是將 M 層與 V 層的實現代碼分離,方便代碼分層設計與維護;從結果形態角度分析,其實 M 層與 V 層能夠理解爲相同信息(或物質)的不一樣表現形態,類比水與冰、或水與氣(可能不恰當,But 我確實理解爲信息/物質形態轉移),而 C 層的存在目的就是爲了鏈接轉移 M 層與 V 層,保證 M 層與 V 層的同步/更新。

那有好奇的朋友就想知道,上面這介紹的 MVC 框架模式,跟咱們 Dao 層有什麼關係呢?

必須有關係

那必須有關係!

咱們知道在 MVC 框架模式中,模型層 Model 是核心的數據信息處理層,包括業務邏輯處理與數據持久化處理,其中業務邏輯處理咱們劃爲 Service 模塊,負責具體業務需求對應的運算邏輯;數據持久化處理咱們劃爲 Dao 模塊(全稱 Data Access Object ,即數據訪問對象),負責與數據庫交互,鏈接 Service 模塊與數據庫。因此只要是跟數據庫打交道,咱們的 Dao 層就必不可少!

Dao

到這裏,我相信不少朋友會聯想到,Dao 模塊是負責數據持久化處理 ,而咱們的 Mybatis 不就是一個持久層框架嗎?沒錯,因此跟數據庫打交道的活,Mybatis 框架絕對是能全權負責,因此當咱們的項目應用集成 Mybatis 框架以後, Mybatis 的增刪改查等 API 就基本在 Dao 模塊中使用,而且接口調用與代碼實現也是極爲簡單便捷。

第3部分,咱們講講本文的關鍵主題 「 Dao 層的兩種實現方式:傳統與代理 」。

三、Dao 層的兩種實現方式:傳統與代理

有了前面兩點做爲基礎,咱們的第三個主題《 Dao 層的兩種實現方式:傳統與代理 》的內容講解會讓你們很容易接受,由於咱們在第一部分主題中花大篇幅闡明 Mybatis 是如何找到 SQL 語句的,讓你們對於 SQL 語句的尋找有了全面的瞭解,因此我在此處先提早跟你們劇透:Dao 層的兩種實現方式:傳統與代理 ,能夠粗糙的理解爲他兩僅僅在SQL 語句的 尋找方式執行對象 上存在區別而已。

Dao層兩種實現方式

咱們先簡單看看咱們通常的工程目錄結構簡例(掐頭去尾只留下基本的 MVC 目錄骨架)。

packages

通常 Dao 層 傳統上 的代碼實現方式:

一、編寫UserDao接口

public interface UserDao {
    
    List<User> findAll() ; 
}

二、編寫UserDaoImpl實現

public class UserDaoImpl implements UserDao {
    
    
    @Override
    public List<User> findAll() {
    
        
        //加載核心配置文件
        InputStream is = Resources.getResourceAsStream("config.xml");

        // 得到sqlSession工廠對象
        SqlSessionFactory fy = new SqlSessionFactoryBuilder().build(is);

        //得到sqlSession對象
        SqlSession sqlSession = fy.openSession();

        // 執行sql語句
        List<User> userList = sqlSession.selectList("dao.UserDao.findAll");
        
        return userList;
       
    }
}

三、編寫 UserMapper.xml

<mapper namespace="dao.UserDao">

    <select id="findAll">
        select * from User
    </select>

</mapper>

四、Dao 層調用 (經過應用程序的 Service 層調用或者直接使用 Junit 框架進行測試)

// Service 服務層調用 
// 或
// Junit 測試框架測試

@Test 
public void tesDaoMethod(){
   
    UserDao userDao = new UserDaoImpl(); 
	List<User> userList = userDao.findAll();
    System.out.println(userList);
}

以上調用結果能夠獲取到全部 User 記錄,這種經過在 Dao層定義接口、並建立 Dao 層接口實現類的方式,咱們通常稱之爲 Dao 層的傳統實現方式,此方式會構建一個接口實現類去做爲 Dao 層的執行對象,而且對於 SQL 語句的找尋方式特別簡單直接,直接指定惟一語句標識,Java 文件中存在硬編碼, 例如本示例中的 SQL 語句惟一標識爲: dao.UserDao.findAll。

dao1

介紹完傳統的開發實現方式,咱們說說 Dao 層的代理開發實現方式吧,首先 Dao 層的代理開發方式有什麼特別呢?

首先代理開發實現方式只需咱們編寫 Dao 接口而不須要編寫實現類。

那麼既然不用編寫實現類,是否是會有一些其它方面的約束呢?

那是固然了,這種代理開發實現方式,要求咱們的接口與配置文件 Mapper.xml 須要遵循一些規範:

1) Mapper.xml 文件中的 namespace 與 mapper 接口的全限定名相同
2) Mapper 接口方法名和 Mapper.xml 中定義的每一個 statement 的 id 相同
3) Mapper 接口方法的輸入參數類型和 mapper.xml 中定義的每一個 sql 的 parameterType 的類型相同
4) Mapper 接口方法的輸出參數類型和 mapper.xml 中定義的每一個 sql 的 resultType 的類型相同

dao1

因爲代理開發實現方式與 Mapper 配置緊密關聯,故此咱們也稱之爲 Mapper 接口開發方法,之因此不須要編寫實現類的緣由是其底層建立了 Dao 接口的動態代理對象,代理對象自己會構建有 Dao 接口的方法體, Dao 層 代理實現方式 的代碼實現方式:

一、編寫UserDao接口

public interface UserDao {
    
    User findOne( int userId ) ; 
}

二、編寫 UserMapper.xml

<mapper namespace="dao.UserDao">
    <select id="findOne" parameterType="int" resultType="user">
    	select * from User where id =#{id}
    </select>
</mapper>

三、Dao 層調用 (經過應用程序的 Service 層調用或者直接使用 Junit 框架進行測試)

// Service 服務層調用 
// 或
// Junit 測試框架測試

@Test 
public void tesDaoMethod(){
   
    
    //加載核心配置文件
    InputStream is = Resources.getResourceAsStream("config.xml");

    // 得到sqlSession工廠對象
    SqlSessionFactory fy = new SqlSessionFactoryBuilder().build(is);

    //得到sqlSession對象
    SqlSession sqlSession = fy.openSession(); 

    //得到MyBatis框架生成的 UserMapper接口的代理類
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 
    
    //代理類執行SQL
    User user = userMapper.findById(1); 
    System.out.println(user); 
    sqlSession.close(); 
}

以上調用結果能夠獲取到了指定 ID 的 User 記錄,此方式經過代理執行實際 SQL 語句,因爲 Dao 接口與 Mapper.xml 配置已經約定好規範,因此不須要在調用接口時指定惟一語句標識,Java 文件中也不會存在硬編碼問題。

到這裏,就會有部分朋友疑惑? sqlSession 會話經過 getMapper 獲取接口代理類以後去調用接口方法,那到底實際執行接口方法的時候,Mybatis 的代理在代碼邏輯上是怎麼跟 mapper.xml 配置文件中的 SQL 語句對應匹配起來的呢?

Dao 代理開發實現方式

上圖黑色 ① ~ ⑥ ,是構建 Dao 代理對象的實際過程,基本就是生成代理對象的過程,其中 MapperProxy 代理類自己實現了 InvocationHandler 接口,因此符合一個代理類的要求,MapperProxy 代理實例最終是指派 MapperMethod 對象進行語句分發執行,包含增刪改查等操做。

上圖紅色 ① ~ ③ ,是代理對象在執行實際接口時根據接口全限定名去 SQL 語句集合池查找 SQL 具體語句的過程。

// 實際語句執行方法對象
public class MapperMethod{
   
    // 根據指令類型分配執行SQL
	public Object execute(SqlSession sqlSession, Object[] args) {
   
        switch (command.getType()) {
   
      		case INSERT: sqlSession.insert(接口語句ID); break;
      		case UPDATE: sqlSession.update(接口語句ID); break;
      		case DELETE: sqlSession.insert(接口語句ID); break;
      		case SELECT: sqlSession.select(接口語句ID); break;
        }
	}   
}

另外,本文關於代理的構建過程,建議你們看一下個人另一個系列一文讀懂系列中的一篇文章 《一文讀懂Java動態代理》,就會對於 JDK 的動態代理有一個深入的理解。(在我我的中心文章列表中查找吧~)

一文讀懂Java動態代理

總結

本篇文章主要圍繞 Dao 層的兩種實現方式展開討論,首先鋪墊一些基礎認識例如 Mybatis 是如何找到 SQL 語句的、以及爲何有 Dao 層,而後咱們集合代碼實現瞭解了傳統開發方式與代理開發方式實現 Dao 層的區別,無非就是傳統方式是經過實現接口構建實現類,而代理模式是經過會話建立代理對象,不過他們只是執行對象不一樣,其實最終執行 SQL 語句仍是須要從 SQL 語句集合池中匹配查找,並最終仍是經過 SqlSession 去執行增刪改查。

本篇完,本系列下一篇咱們講《 Mybatis系列全解(八):Mybatis的動態SQL 》。

文章持續更新,微信搜索「潘潘和他的朋友們」第一時間閱讀,隨時有驚喜。關於熱騰騰的技術、框架、面經、解決方案、摸魚技巧、教程、視頻、漫畫等等等等,咱們都會以最美的姿式第一時間送達,歡迎 Star ~ 咱們將來 不止文章!想進讀者羣的朋友歡迎撩我我的號:panshenlian,備註「加羣」咱們羣裏暢聊, BIU ~

相關文章
相關標籤/搜索