MyBatis開發,你用 xml 還是註解?

2021-01-12 計算機java編程

最近在看公司項目時發現有的項目mybatis是基於註解開發的,而我個人的習慣是基於xml文件開發。

對於mybatis註解開發的原理理解不夠,於是翻閱了部分源碼,寫下此文。主要介紹了mybatis開發的兩種形式、三種寫法。還有一點瞎思考,介紹了一處騷代碼、還有一個坑。

原創不易,感謝閱讀,感謝關注,感謝點讚,感謝轉發。

兩種形式,三種寫法

最近在看公司的一些項目的時候發現有的項目裡面的 mybatis 是基於註解開發的。而我個人的習慣是基於 xml 文件開發。

所以對於基於註解開發的原理不太了解,於是去翻看了一下相關源碼,形成此文。

本文主要介紹基於 mybatis 開發的兩種形式,三種寫法。

其中兩種形式是指:

1.基於 xml 文件。

2.基於註解開發。

三種寫法是指除了 xml 的形式外,註解又有兩種不同的寫法,它們的實現原理也略有不同,拿 Select 語句舉例,就有兩種註解 @Select、@SelectProvider 。

演示示例

先上一個演示示例給大家直觀的感受一下:

首先,我們有個用戶表,包含這些欄位和這樣一條數據:

然後我們搞個接口類,用三種方式去查詢用戶的年齡,具體如下:

xmlQueryAgeByName 方法是使用 xml 的方法去查詢用戶年齡,對應的 xml 如下:

annotationQueryAgeByName 方法是使用 @Select 註解去查詢用戶的年齡,SQL 就寫在註解裡面:

classQueryAgeByName 方法是使用 @SelectProvider 註解去查詢用戶的年齡,可以看到註解裡面有個 type 欄位,對應一個 class 類。一個 method 欄位,對應 class 類中的一個方法:

其中 UserInfoSql 類如下:

然後,再來一個測試用例,把三個方法都測試一下:

最後的輸出結果如下:

xmlQueryAgeByName whyAge = 18annotationQueryAgeByName whyAge = 18classQueryAgeByName whyAge = 18

測試用例就演示完成了,是一個極簡的用例。

我就是基於這個案例去分析源碼的,在分析之前,其實有點經驗的老哥也能看出來了,我們先撇開常規的 xml 文件的形式不談。

基於 @Select 註解的接口, SQL 就在註解裡面,所以我們只需要通過反射取出註解裡面的 SQL 進行分析就行了。

基於 @SelectProvider 註解的接口,SQL 雖然在一個類的方法中,但是註解上都告訴你是哪個類的哪個方法了,所以,一定是基於反射去取出方法裡面的 SQL 的。

接下來,我們就是去驗證一下。

小心求證

首先,我先問你一個問題。SpringBoot 是怎麼加載 mybatis 的?

熟悉 SpringBoot 啟動過程的朋友知道,SpringBoot 會去加載mybatis-spring-boot-autoconfigure-x.x.x.jar下 META-INF 中的spring.factories文件:

所以,下面的 sqlSessionFactory 方法就是我們的入口處:

入口給你找到了,你可以直接在這裡加上斷點開始 debug 了。

我知道,雖然是剛剛開始,但是可能有些讀者覺得已經超綱了。但是沒有關係的,繼續看下去,我這裡只是給你說個入口在哪而已。

由於 debug 的過程不是文本重點,這裡就不去介紹了。debug 的時候我們會看到這個方法:

org.apache.ibatis.builder.xml.XMLMapperBuilder#parse

這個方法的第 92 行,就是我們的 xml 內容:

然後在下面這個方法中對 xml 文件進行瘋狂的解析:

org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode

圖片可以點開看大圖哦,debug 模式,可以看到一些輸出:

上面的源碼的第 94 行,獲 取 SqlSource 很關鍵,要好好看看,這裡調用了這個方法:

org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)

接著在下面方法的第 52 行,剝離出整個完整的 sql:

org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode

上面就是常規的 xml 形式的 SQL 原始語句(變量、條件表達式都還未進行替換,不可直接執行的 SQL)獲取過程,不是本文重點,簡單的分析一下就行。

接下來繼續 debug 的時候會遇到下面這個方法,看包名你就知道,這就是我們關心的註解解析相關的方法了:

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse

在這個方法裡面,會去循環處理 mapper 類中的方法:

接下來,就會遇到這個方法了:

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#getSqlSourceFromAnnotations

當循環到 annotationQueryAgeByName 方法的時候,下面方法的一些關鍵參數如下所示:

首先我們看 428 行,解析到了 sqlAnnotationType 為 Select:

所以會進入下面的 if 分支,然後運行到 435 行,通過反射獲取到了 @Select 註解上的 SQL 語句:

繼續往下走,通過 436 行,我們可以走到這個方法:

org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, java.lang.String, java.lang.Class<?>)

這個方法就有點意思了,進來判斷了 script 即 SQL 是否是以 script 腳本開頭的,如果是,則走的和之前 xml 一樣的解析邏輯:

我第一次看到這個地方的時候,一下才恍然大悟過來,我才明白,@Select 的本質還是 xml 文件的形式啊。只是換了個展現形式而已。

我之前的一個問題,或者說是錯誤的看法也就迎刃而解了。

我之前認為 @Select 的方式是只能支持簡單 SQL 的書寫,對於一些類似於判空的需求是不支持的。(因為對 mybatis 註解開發確實不熟)

比如在 xml 文件中這樣去寫:

<when test='startPage !=nulland pageSize != null '> LIMIT #{startPage},#{pageSize}</when>

只是這個寫法,呃,怎麼說呢,非常不優雅。

不要為了註解而註解,很明顯,這種情況直接用 xml 形式更好。

到這裡,我們也知道了,基於 @Select 註解的方式開發時, mybatis 會通過反射獲取到註解裡面的 SQL ,而這些 SQL 需要一些比較複雜功能,比如判斷條件是否為空時,可以用 script 標籤包裹起來。寫法和在 xml 裡面開發是一樣的。

接下來,我們看看 @SelectProvider 方法是什麼個樣式。

還是在同樣的方法中,只是走向了另外一個分支:

此時的 sqlProviderAnnotation 裡面的東西如下:

接著去 new ProviderSqlSource 對象:

在這個方法中,獲取到了註解上的具體的提供 SQL 原始語句的方法。

注意紅框中框起來的 providerMethod 對象,後面獲取真正執行的 SQL 語句的時候還會用到。

同時,我們可以看到 ProviderSqlSource 是 SqlSource 的實現類。

所以,不管是 xml 還是註解,最終都需要獲取到一個 SqlSource 對象。

而在本文的示例代碼中, xml 和 @Select 生成的是 RawSqlSource。

@SelectProvider 生成的是 ProviderSqlSource。他們裡面放的東西是不一樣的。

在 RawSqlSource 裡面的 sqlSource 變量(類型 StaticSqlSource)放的已經是從 xml 或者 @Select 註解中獲取到的 SQL 原始語句了(但是裡面的變量還沒替換,因為程序啟動過程中根本不知道變量的值具體是什麼,如果有一些條件表達式的話同理)。

而在ProviderSqlSource 裡面,我們前面已經說了,放的是 @SelectProvider 註解上具體的提供 SQL 語句的方法,僅僅是方法,而不是語句。

前面的所有分析都是在我們的方法真正執行之前,接下來,才會 debug 到我們的測試用例,因為只有我們的測試用例裡面才有真正的入參, mybatis 才能根據入參,執行最終的 SQL 語句。

進入 getBoundSql 我們可以看到第292行,就是通過 sqlSource 的 getBoundSql 方法獲取到的 boundSql 對象:

org.apache.ibatis.scripting.xmltags.DynamicSqlSource#getBoundSql

這不就又呼應上了嗎?又看到 sqlSource 了。

所以,接下來,我們看一下這兩個方法就可以了:

org.apache.ibatis.builder.StaticSqlSource#getBoundSqlorg.apache.ibatis.builder.annotation.ProviderSqlSource#getBoundSql

首先看一下 StaticSqlSource 的實現:

裡面的一些關鍵參數如下:

首先可以 sql 變量,裡面是一條待加工的 SQL 語句,我們前面已經分析過了,程序啟動的過程中,這裡為什麼不替換呢?

因為不知道換成啥呀。

那你覺得在這個地方會替換嗎?

還是不會的。雖然我們已經告訴 mybatis , userName 就是 why 了,但如果在這個地方把 why 帶到 SQL 裡面去,我們倒是可以獲得一個完整的正確的 SQL。

但是,如果我們傳入的是 「why or 1=1」呢?

這是什麼東西我相信你一下就恍然大悟了吧,SQL 注入呀。

另外插一句,如果想看 SQL 注入的情況,就是走到 DynamicSqlSource 的情況,在 xml 中把 # 換成 $ 就行,有興趣的可以試一試。

我這裡只是給你截個圖,瞅一眼:

好了,我們接著剛才繼續說。

繼續 debug 會走到這方法中去:

org.apache.ibatis.executor.SimpleExecutor#doQuery

而這個方法的第 62 行,prepareStatement,這個東西不用說了吧,從學 JDBC 的時候就用上它了,老朋友了:

最後去執行真正的查詢操作,處理返回值。

接著看 ProviderSqlSource 的實現,注意看我圈起來的那部分的分支判斷:

無非就是判斷有幾個參數,反射方法調用的時候需要怎麼傳參而已。最終會調用到這個方法裡面來獲取 SQL 語句:

可以看一下這個時候 providerMethod 和 sql 變量分別是什麼:

而這裡這個 providerMethod 怎麼來的知道了吧?我們前面剛剛分析過了。

new ProviderSqlSource 對象的時候,我還專門說了:「注意紅框中框起來的 providerMethod 對象,後面獲取真正執行的 SQL 語句的時候還會用到。」

就是在這個地方用到的。

你看,又呼應上了。

這個時候,我們獲取到了原始的 SQL 語句了,也有參數了,這樣的場景和我們剛剛分析的情況就一模一樣了,所以後面的邏輯都一樣,進行了代碼復用:

進入第 98 行,也就是下面這個我們之前分析過的方法:

org.apache.ibatis.builder.SqlSourceBuilder#parse

在這個方法中,返回了一個 StaticSqlSource 對象:

再次呼應,流程是一樣一樣的。

另外,再說一下,用 @SelectProvider 註解時的 class 對象裡面的方法還可以這樣去寫,有興趣的可以去研究一下:

好了,我們的論證部分就算是完了,我發現這個東西,用視頻真的幾分鐘就講清楚了,描述起來還是有點困難的,難道是在逼我當UP主嗎?

不知道大家看的是否明白了,如果對 mybatis 了解不多的朋友可能看起來有一點吃力,但是沒有關係,你就把這篇文章當做一個導讀,然後自己搞個 Demo 跑起來,玩一玩就行。

個人思考

其實在寫這篇文章的時候我就產生了一個思考。

mybatis 為什麼要去支持註解呢?

當然,我們都知道,基於註解開發是趨勢,給我們簡化了非常多的東西。

特別是 SpringBoot 的出現,可以說是註解開發的黃金時代。

遙想當年剛剛入行的時候,開發一個 SSM 項目大多數時間都是在進行 xml 文件的配置。

可以說是很羨慕現在入行的小年輕了,沒有真正經歷(也許自己搭建過,玩了一下)過被 xml 配置支配的恐懼。

在 xml 時代,大家都是粘來粘去的。而現在基於註解開發了,很多東西都簡化了,漸漸的自己也能很輕鬆的搭建一個可以跑起來的小項目了。

所以,基於註解開發大體上一件很優雅,很好,很值得推廣的事情。

為什麼說大體上呢?

因為我個人偏見的覺得對於 mybatis 框架來說,沒有 xml 文件的 mybatis 是沒有靈魂的。

當然,如果你全是簡單的 SQL 語句就能實現的功能,你可以用註解開發。但是這個情況,我覺得還是在少數的。

同樣,我們可以用註解的形式實現所有 xml 文件能實現的功能。但是我覺得不太優雅。

所以,我覺得一個比較折中的方式是簡單 SQL 可以用註解開發,如果是一些有諸如條件判斷類的需求的 SQL 還是要寫在 xml 文件中。

不要為了擁抱註解,而完全摒棄了 xml 的形式。

你記得嗎,在 xml 時代轉向註解時代的時候,還有一個經常用到的註解。

有人說這是過渡時代的產物,而在我看來,這更是求同存異的完美體現。

這個註解,就完全的體現最近這句很火的話:

君子美美與共,和而不同。

當然這些都是我在寫這篇文章的過程中產生的一些淺顯的個人看法而已。不具備參考意義。

騷代碼

另外,再給大家分享一個我認為的 mybatis 的騷代碼吧。

代碼非常的簡單明了,很久以前第一次看 mybatis 源碼的時候我就是覺得有點「騷」,給我留下了深刻的影響:

org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)

selectOne 方法:

該方法調用的還是 selectList 方法,但是對返回集合進行了一個判斷,如果集合大小為 1,說明就真的是 selectOne ,如果大於 1,則拋出異常。

說真的,如果讓我去實現這個功能,我不會一下就想到這個方法,我會去老老實實的寫功能,然後對返回值進行判斷。寫完之後,我可能才會發現。哎,這段代碼和 selectList 方法可以復用哦,然後才提取出來,變成這樣。

記得很久之前面試,面試官問我對看過的源碼中哪段影響深刻的,其中我就說到了這個方法。

總之,我個人覺得很妙。

注意坑

然後再說一個之前踩過的坑吧,還導致了一次緊急上線。

還是拿文中的示例說明:

如果我們把返回值從 Integer 變成 int:

用這個測試用例還是會正常查詢出結果:

但是,如果我們查詢一個資料庫中不存在的人的年齡呢?比如這樣:

那麼就會拋出這樣的錯誤:

找到對應源碼,我們可以看到:

當返回值是 null 的時候,但是方法上的返回值類型又不是包裝類型中的一種,也不是 void 類型,則拋出異常。

看一下這個方法,是 native 的:

java.lang.Class#isPrimitive

你想想為什麼 mybatis 給你進行了這樣的一個判斷呢?

那就是如果返回為 null ,自動拆箱的時候會拋出空指針的。

即使 mybatis 幫我們擋了一下,我還是完美的踩了一個坑,寫出了空指針異常。

代碼是這樣的,接收的時候我還是用 Integer 去接收了:

但是接口調用時的返回值我手賤寫成了這樣:

明白了吧,妥妥的,空指針,沒得跑了。

相關焦點

  • mybatis開發,你用 xml 還是註解?我 pick xml
    最近在看公司項目時發現有的項目mybatis是基於註解開發的,而我個人的習慣是基於xml文件開發。對於mybatis註解開發的原理理解不夠,於是翻閱了部分源碼,寫下此文。主要介紹了mybatis開發的兩種形式、三種寫法。還有一點瞎思考,介紹了一處騷代碼、還有一個坑。
  • Mybatis 中xml和註解映射,原來如此簡單
    在實際開發中,這種常見是在所難免。我們可以使用下面的這種方式解決。註解方式九個頂級映射元素對應註解:其他部分註解是配合九個註解進行使用的。select註解把本地的UserMapper.xml刪掉,然後改一下mybatis-config.xml,把其中的UserMapper.xml給注釋掉。
  • 別用註解了!教你用 Springboot 整合Mybatis
    雖然說只用Spring boot也可以開發,但是對於多表多條件分頁查詢,Spring boot就有點力不從心了,所以LZ把Mybatis整合進去,發現這樣工作事半功倍!後悔沒早搭建了!!本文主要是講解下 Springboot 如何整合 MyBatis,這裡使用的是xml配置SQL而不是用註解。
  • Mybatis基本知識十九:註解式開發-動態Sql註解開發
    上一篇文章:《Mybatis基本知識十八:Mybatis註解式開發-多表註解式開發》若文中有紕漏,請多多指正!!!1.前言前面講解了單表、多表的註解式開發,本章節主要講解Mybatis通過註解的方式實現動態sql。
  • Mybatis基本知識十八:Mybatis註解式開發-多表註解式開發
    上一篇文章:《Mybatis基本知識十七:Mybatis註解式開發-單表註解式開發》若文中有紕漏,請多多指正!!!1.前言上一章節主要是MyBatis註解的一個入門案例,在實際開發中關聯關係查詢是在經常不過的事情,本章節主要講解演示在關聯關係查詢時,應該怎樣使用MyBatis進行註解開發,本章節主要講解內容包括:
  • Mybatis第三講 緩存與註解
    這裡採用的是LRU, 移除最長時間不用的對形象 flushInterval:刷新間隔時間,單位為毫秒,這裡配置的是100秒刷新,如果你不配置它,那麼當 SQL被執行的時候才會去刷新緩存。 size:引用數目,一個正整數,代表緩存最多可以存儲多少個對象,不宜設置過大。設置過大會導致內存溢出。
  • 小學妹問:Mybatis常見註解有哪些?
    @Mapper該註解目的就是為了不再寫mapper映射文件 (UserMapper.xml)。可以大大的簡化編寫xml的繁瑣。增刪改查註解總結其他註解@Results:結果映射的列表, 包含了一個特別結果列如何被映射到屬性或欄位的詳情。書 性:value, id。value 屬性是 Result 註解的數組。對應xml中的<resultMap> 標籤。
  • Mybatis中mapper的xml解析詳解
    上一篇文章分析了mapper註解關鍵類MapperAnnotationBuilder,今天來看mapper的項目了解析關鍵類XMLMapperBuilder。基礎介紹回顧下之前是在分析configuration的初始化過程,已經進行到了最後一步mapperElement(root.evalNode("mappers")),這個方法裡有兩種解析mapper的方法,一種是解析類,一種是解析xml文件,上一篇文章在講解析類中的註解,今天說到的就是解析xml
  • 從零開始學SpringBoot之MyBatis-註解
    需求來源:有人問:「SpringBoot會將mybaits配置文件sqlmapconfig.xml的視頻與mapper.xml集成嗎???看到直接支付的集成,結果是快速開發模式,SQL是在類中編寫的,想看看配置模式之神創建一個嗎?」粉絲需要,這才是真正的需要。好吧,胡說太多了,不好,不好。讓我們直說重點。
  • 詳解mybatis和Mybatis-Plus區別
    通俗來講——MyBatis:一種操作資料庫的框架,提供一種Mapper類,支持讓你用java代碼進行增刪改查的資料庫操作,省去了每次都要手寫sql語句的麻煩。但是!有一個前提,你得先在xml中寫好sql語句,是不是很麻煩?於是有下面的↓Mybatis Generator:自動為Mybatis生成簡單的增刪改查sql語句的工具,省去一大票時間,兩者配合使用,開發速度快到飛起。
  • Mybatis第四講 插件開發
    mybaits 需要程式設計師自己編寫 sql 語句, mybatis 官方提供逆向工程 可以針對單表自動生成 mybatis 執行所需要的代碼 (mapper.java,mapper.xml..)實際開發中,常用的逆向工程方式:由資料庫的表生成java代碼。
  • MyBatis:緩存,延遲加載,註解應用
    OrderMapper.xml<!>這幾年來註解開發越來越流行,MyBatis 也可以使用註解開發方式,這樣我們就可以減少編寫 Mapper 映射文件了。//DTD Config 3.0//EN&34;http://mybatis.org/dtd/mybatis-3-config.dtd&34;jdbc.properties&34;lazyLoadingEnabled&34;false&34;lazyLoadTriggerMethods&34;toString()&34;cacheEnabled&34;true&34;com.renda.domain
  • Spring註解配置和xml配置優缺點比較
    Spring註解配置和xml配置優缺點比較在昨天發布的文章《spring boot基於註解方式配置datasource》一文中凱哥簡單的對xml配置和註解配置進行了比較。然後朋友看到文章後,就問:那你說說這兩種區別。額,說真的,還真把凱哥給問蒙圈了。
  • 如何讓 Mybatis 自動生成代碼,提高開發效率
    每日掏心話  你給了生活意境,那麼生活才能給你風景。你風聲鶴唳,生活也就只好四面楚歌。  1.2 配置 generator.xml  其實名字無所謂, 只要跟下面的pom.xml文件中的對應上就好了。
  • SpringMVC+Mybatis 框架介紹
    一、Spring MVC框架要點1、為什麼選用SpringMVC(1)簡單易用,學習成本低,開發效率高(2)性能靈活,優於Struts(3)大眾框架,遇到問題網上有很多解決方案2、SpringMVC的註解類(1)@Controller註解定義控制器(2)@RequestMapping
  • Mybatis中mapper相關註解解析類詳解
    基礎介紹根據MapperAnnotationBuilder和XMLMapperBuilder兩個的名字大概也可以猜出來他們的作用,MapperAnnotationBuilder應該是來處理mapper註解的,而XMLMapperBuilder是來處理mapper.xml文件的。
  • mybatis-plus思維導圖,讓mybatis-plus不再難懂
    雖然說單表的增刪改查操作可以通過mybatis generator工具來生成(或者自己寫模板工具生成),但項目開發的過程中總免不了要新添加新欄位,這些工具就幫不了我了,我得把新欄位寫到原來的所有增刪改查的sql中。這是個痛苦的過程,特別是當你重複了很多次之後。
  • 你確定會?Spring集成MyBatis | Spring系列第53篇
    1、本文內容【文末送書】本文主要介紹mybatis和spring集成的兩種方式,對MyBatis不熟悉的,建議先看一下MyBatis高手系列目前註解的方式我們用的比較多,所以主要介紹註解的方式,xml的方式這裡就暫時不介紹了。
  • 如何用Spring+Mybatis快速搭建1個微服務
    Spring Boot設計目的是用來簡化新Spring應用的初始搭建以及開發過程。該框架使用了特定的方式來進行配置,從而使開發人員不再需要定義樣板化的配置。【資料】發給你~一起學習進步!用一句話來介紹Spring Boot的好處是使用配置文件來簡化編碼,這裡的編碼不僅僅值代碼,還包括各種xml文件。
  • 基於 SpringBoot2.0+優雅整合 SpringBoot+Mybatis
    基於最新的 SpringBoot2.0+,是你學習SpringBoot 的最佳指南。) ,歡迎各位 Star。SpringBoot 整合 Mybatis 有兩種常用的方式,一種就是我們常見的 xml 的方式 ,還有一種是全註解的方式。我覺得這兩者沒有誰比誰好,在 SQL 語句不太長的情況下,我覺得全註解的方式一定是比較清晰簡潔的。