從Mybatis源碼分心詳解你不知道的Mybatis用法和細節

2020-12-16 計算機java編程

強大的結果處理器ResultHandler

DO轉VO--常用方式

通常情況下,我們的持久層的對象不會(不應該)直接響應給調用者,需要轉換為 VO 對象再響應出去。基於本系列博客的使用例子,這裡假設我需要在 web 層返回下面的 VO 對象,如下。在這個類中,除了員工表的欄位外,還包括了部門表的欄位。

web 層的操作大致是這樣的,我先查詢出Employee的集合,然後再進行對象轉換。

DO轉VO--ResultHandler方式

使用 Mybatis 的話,其實還有另外一種方案來處理 DO 轉 VO 的問題,就是採用結果處理器--ResultHandler,如下。

這是一個接口,實現類需要我們自己定義。作為測試例子,這裡我簡單定義了一個。

使用ResultHandler時,Mapper 接口的方法定義需要調整,入參需傳入ResultHandler,且返回值必須為 void。至於 xml 對應的方法內容,還是和常用方式一樣,不需要更改。下面兩個方法共用一個 xml 的 select 節點不會出問題的,這一點不用擔心。

最後回到我們的 web 層,至於 service 層的代碼就忽略不看了。當調用 service 層時,我已經拿到了轉換好的 VO 對象,我不需要再做處理。

當 Mapper 接口的方法入參包含ResultHandler且返回類型為 void,Mybatis 會對這種情況特殊處理:當遍歷結果集進行映射時,每映射完一個對象都會調用一次ResultHandler並將映射好的對象傳入,這時,我們可以隨意地對對象進行處理,包括我們常見的 DO 轉 VO,當然,它的功能並不局限於此。

分頁不需要插件RowBounds

本系列使用篇中提到使用 pagehelper 來支持分頁功能,本質上是使用了插件對 sql 植入分頁參數。其實,Mybatis 已經提供了RowBounds這類來支持分頁功能,這種方式不需要安裝插件,MybatisPlus 本質上就是使用了這種方式。

和ResultHandler一樣,我們只需要改造下 Mapper 接口的方法,如下。

List<Employee> selectByCondition(@Param("con") EmployeeCondition con, RowBounds rowBounds);

這裡我簡單編寫個測試類,直接使用RowBounds對象,實際上最好對RowBounds進行更多的包裝。

測試以上代碼,可看到打出的語句植入了分頁參數:

相比使用插件,這種方式是否更加簡單呢?

延遲加載

我們知道,在 resultMap 中使用嵌套 select 查詢,並且全局聲明使用懶加載,可以實現按需加載嵌套屬性。

還是回到使用篇中例子,mapper 的配置如下,員工對象關聯了部門(一對一)、角色(一對多)、菜單(一對多):

測試代碼中,我們注釋掉第1、3 和 4 點的代碼,即只調用getDepartment()方法。

測試以上代碼,可以看到,只有部門被加載出來,而角色和菜單沒有,很好地實現了按需加載。

接著我們放開第 1 點,即增加列印員工,注意,使用例子中我並沒有重寫toString()方法,所以方法中也不會用到關聯對象。

測試以上代碼,我們驚訝地發現,這時部門、角色和菜單都被列印出來了,說好的按需加載呢?

這就很奇怪了,我調用的方法並沒用到關聯對象啊,為什麼它們會被加載出來?

什麼時候觸發延遲加載

在上面的例子中,我們的按需加載失效了嗎?

其實並沒有,對於 Mybatis 而言,它可以知道getDepartment()這樣的方法會使用到關聯對象,但是toString()這樣的方法,它就沒辦法知道了。考慮我們會在重寫toString方法時使用到嵌套對象,所以,Mybatis 默認這個方法會觸發延遲加載。同樣道理,equals(),clone(),hashCode()等方法也是一樣的,項目中要重點關注equals()和hashCode()。

那麼,我們如何控制這種行為呢?Mybatis 提供了 lazyLoadTriggerMethods 配置項指定對象的哪些方法觸發延遲加載:

我們將配置修改如下:

<settingname="lazyLoadingEnabled"value="true" /><settingname="lazyLoadTriggerMethods"value="equals,clone,hashCode" />

再次測試上面的例子。這時,嵌套對象都沒有被加載出來。

這裡再補充下,還有另一個配置項 aggressiveLazyLoading 也會影響延遲加載的觸發,這個配置項在 3.4.1 之後我們保持默認就行,如果不是必須,強烈建議不要配置成 true。如果你將 aggressiveLazyLoading 配置為 true,即使你只是 getId() 也會將所有嵌套對象加載出來

作為延遲加載部分的總結,這裡對比下不同配置項組合的效果:

有的延遲?有的不延遲

如果我希望部分關聯對象不用延遲加載,部分關聯對象又需要,例如,查詢員工對象時,部門跟著查出來,而角色等到需要用的時候再加載。針對這種情況,可以在映射關係中使用 fetchType來覆蓋延遲加載的開關狀態:

嵌套結果映射的一個大坑

嵌套結果裡如果是collection的話,分頁總數會存在問題,所以,嵌套結果映射的方式最好僅針對 association 使用

當時我沒有解釋具體原因,這裡我補充下吧。

錯誤的總數

mapper 的 resultMap 是這樣配置示例:

編寫測試方法如下。這裡會採用分頁插件 pagehelper 來統計查詢總數,及進行分頁。如果使用RowBounds,也不影響測試結果。注意,資料庫中的「zzs001」只有一條記錄,所查詢到的總數和映射對象都會是一條。

測試代碼,可以看到分頁統計的總數和實際數量都會是一條,完全沒問題。

接下來我再 resultMap 中增加一個 collection 類型的嵌套對象。

放開測試代碼中的注釋,測試如下。映射對象一條,沒錯,但是查詢總數,竟然是 2 條???

這就是我提到的嵌套結果映射的一個大坑。

原因分析

難道是統計錯了?讓我們執行下控制臺的 sql,記錄竟然也是 2 條,哪裡冒出來的???

其實,根本原因確實出在我們的使用方法上,collection 的嵌套結果映射就不應該被用在涉及到統計的場景。我們的 sql 查出來有兩條,仔細觀察就會發現,這兩條記錄的 id 是一模一樣的,我們再查詢出 1 個欄位:

看到這裡應該就明白了吧,統計出錯主要是聯表造成的。員工和角色是一對多的關係,當員工擁有多個角色時聯表查詢將出現比員工數量更多的記錄,而這些記錄,在 Mybatis 映射對象時會將其合併起來。

這就造成了所謂的錯誤總數問題。所以,collection 的嵌套結果映射並不適合統計場景。

自動映射

開啟自動映射

mybatis 的結果自動映射默認是開啟的,可以在使用 setting 配置項進行修改,它有三種自動映射等級:

NONE - 禁用自動映射。僅對手動映射的屬性進行映射。PARTIAL - 對除在內部定義了嵌套結果映射(也就是連接的屬性)以外的屬性進行映射。默認配置。FULL - 自動映射所有屬性。默認使用 PARTIAL,另外, 無論設置的自動映射等級是哪種,你都可以通過在映射文件中設置 resultMap 的 autoMapping 屬性來為指定的結果映射設置啟用/禁用自動映射。

自動映射駝峰命名的屬性

當自動映射查詢結果時,MyBatis 會獲取結果中返回的列名並在 Java 類中查找相同名字的屬性(忽略大小寫)。如果列名和實體中的屬性名對不上,則需要顯式地配置。在使用例子中,我們使用resultMap來映射表和對象,如下:

除了表列名和實體的屬性名一致的情況,其他的欄位都需要我們手動配置映射,這樣做比較麻煩。但是,大部分情況下,我們都會遵循駝峰命名的規則來定義實體的屬性名,是否可以直接通過這種規則來自動映射呢?

mybatis 提供了mapUnderscoreToCamelCase配置項來處理這種情況。

相關焦點

  • 看到Mybatis源碼就感到煩躁,怎麼辦?
    背景最近,聽到很多吐槽:看到源碼,心中就感到十分糾結、特別煩惱。為什麼糾結?因為面試的時候,面試官很喜歡問:你看過什麼框架源碼?JDK源碼也行。這時候,如果回答沒有看過,雖然沒讓你立馬回去等通知。大部分人的情況是:源碼不是沒有看過,而是每次只看得下一部分。為什麼只看得下一部分呢?通常有下面三種原因:缺乏技術支撐。看源碼是需要技術支撐的,不是隨便一個小白也能看懂的。沒有一些技術支撐,你頂多看看一小段,然後就看不下去,於是就放棄了。
  • Mybatis的SqlSession創建過程詳解
    前面mybatis的初始化過程分析完成,接下來是第二步SqlSession的創建。創建過程總覽SqlSession創建過程如下圖:創建過程還是比較簡單的,首先是之前分析的SqlSessionFactory,在mybatis中提供了兩個SqlSessionFactory實現:SqlSessionManager和DefaultSqlSessionFactory
  • MyBatis動態Sql之choose,where,set標籤的用法
    添加otherwise條件後,由於where條件不滿足,因此在這種情況下就查詢不到結果。假設有這樣1個需求:根據用戶的輸入條件來查詢用戶列表,如果輸入了用戶名,就根據用戶名模糊查詢,如果輸入了郵箱,就根據郵箱精確查詢,如果同時輸入了用戶名和郵箱,就用這兩個條件去匹配用戶。
  • 深入理解 Mybatis 插件開發
    關於Mybatis插件,大部分人都知道,也都使用過,但很多時候,我們僅僅是停留在表面上,知道Mybatis插件可以在DAO層進行攔截,如列印執行的
  • Mybatis中mapper相關註解解析類詳解
    上一篇文章分析發現解讀mapper關鍵是兩個類MapperAnnotationBuilder和XMLMapperBuilder,今天先來看MapperAnnotationBuilder。接著上一篇通過掃描接口添加mapper的方法會創建MapperAnnotationBuilder並執行parse方法,具體源碼如下圖:MapperAnnotationBuilder關鍵屬性說明:statementAnnotationTypes:靜態屬性,存有各種sql對於在mybatis
  • (四)Mybatis從入門到入土——別名、配置文件以及引入mapper
    別名的用法    使用別名之前需要先在mybatis中註冊別名,而註冊別名有3種方式。別名不區分大小寫mybatis默認為很多類型提供了別名別名的原理    mybatis允許我們給某種類型註冊一個別名,別名和類型之間會建立映射關係,這個映射關係存儲在一個map對象中,key為別名的名稱,value為具體的類型,當我們通過一個名稱訪問某種類型的時候,mybatis根據類型的名稱,先在別名和類型映射的map中按照key進行查找,如果找到了直接返回對應的類型
  • MyBatis-Plus為啥這麼牛?
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫前言大家有用過MyBatis-Plus(簡稱MP)的都知道它是一個MyBatis的增強工具,旨在MyBatis的基礎上只做增強不做改變
  • mybatis源碼分析-反射基礎MetaClass
    WHAT我們知道mybatis是一個ORM(Object Relation Mapping)框架,既然是ORM框架,那麼資料庫與java的pojo之間的相互映射必然是其重要工作之一。我們知道,從資料庫的一個結果映射到java bean中,我們就以mybatis的resultMap配置為例<resultMap id="BaseResultMap" type="com.ruoyi.business.pojo.Shop" ><
  • MyBatis JPA Extra,MyBatis JPA 擴展 v2.2 發布
    相關資源MyBatis網站MyBatis GitHub源碼1、JavaBean注釋簡單只支持6個注釋 @Entity @Table @Column @Id @GeneratedValue @;import org.apache.mybatis.jpa.test.domain.Students;import org.apache.mybatis.jpa.util.WebContext;import
  • Mybatis中mapper的xml解析詳解
    ,初始化XMLMapperBuilder方法的流程與加載mybatis的配置文件關鍵類XMLConfigBuilder及其相似,都是通過xml文件創建XPathParser對象作為XMLMapperBuilder對象的parse屬性。
  • Mybatis 中xml和註解映射,原來如此簡單
    如果拿它跟具有相同功能的JDBC 代碼進行對比,你會立即發現省掉了將近 95% 的代碼。 致力於減少使用成本,讓用戶能更專注於 SQL 代碼。來自官網。注意:和本質是一樣的,都是Map數據結構,但是二者不能同時存在。增刪改案例insert從這裡可以知道,關於增加insert是沒有返回值類型可以讓我們指定的。默認返回int類型。
  • 寫了10年的代碼,我最怕寫Mybatis這些配置,現在有詳解了
    過程中, 當手寫 JavaBean和XML 寫的越來越多的時候, 就越來越同意出錯。這種重複性的工作, 我們當然不希望做那麼多。還好, mybatis 為我們提供了強大的代碼生成--MybatisGenerator。通過簡單的配置, 我們就可以生成各種類型的實體類, Mapper接口, MapperXML文件, Example對象等。
  • mybatis中trim標籤的使用
    這條 SQL 最終會是這樣:SELECT * FROM BLOGWHERE AND title like 『someTitle』你可以使用where標籤來解決這個問題,where 元素只會在至少有一個子元素的條件返回 SQL 子句的情況下才去插入「WHERE
  • Mybatis初始化過程簡單總結
    前面連續多篇文章都是在數據mybatis的初始化過程,目前基本完成,是時候做一個總結了。所以SqlSessionFactory的初始化實際上是mybatis的全局配置類Configuration的初始化。而它的初始通過XMLConfigBuilder的parse方法實現。
  • Mybatis的sql組裝詳解
    Sql來源從上一篇的最後一步執行sql那裡倒推sql的來源,源碼主要過程如下圖:可以看到最後是通過BoundSql直接獲取的sql,然後往前倒推最後發現是通過MappedStatement的getBoundSql方法返回的。
  • Mybatis【配置文件】
    映射文件配置文件和映射文件還有挺多的屬性我還沒有講的,現在就把它們一一補全在mapper.xml文件中配置很多的sql語句,執行每個sql語句時,封裝為MappedStatement對象,mapper.xml以statement為單位管理sql語句Statement的實際位置就等於namespace+StatementId
  • MyBatis 動態 SQL 詳解(以後寫 SQL 爽多了)
    如果你使用過 JDBC 或其它類似的框架,你應該能理解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最後一個列名的逗號。利用動態 SQL,可以徹底擺脫這種痛苦。
  • 技巧:MyBatis 中的trim標籤,好用!
    作者 | wt_better來源 | https://blog.csdn.net/wt_better/article/details/80992014mybatis的trim標籤一般用於去除sql語句中多餘的and關鍵字,逗號,或者給sql語句前拼接 「where「、「set「以及「values(「 等前綴,或者添加「)「等後綴,可用於選擇性插入、更新、刪除或者條件查詢等操作
  • Mybatis中trim標籤的使用教程
    mybatis的trim標籤一般用於去除sql語句中多餘的and關鍵字,逗號,或者給sql語句前拼接 「where「、「set「以及「values
  • 你用過MyBatis的discriminator鑑別器映射嗎?
    所以我們的需求為:根據用戶id查詢用戶擁有的角色列表,如果角色是啟用的,就繼續查詢出角色對應的權限列表,如果角色是禁用的,就不需要查詢對應的權限列表。"> <collection property="sysPrivilegeList" fetchType="lazy" column="{roleId=id}" select="com.zwwhnly.mybatisaction.mapper.SysPrivilegeMapper.selectPrivilegeByRoleId