1. 程式人生 > 其它 >魚和熊掌兼得:同時使用 JPA 和 Mybatis

魚和熊掌兼得:同時使用 JPA 和 Mybatis

技術標籤:javajava基礎java程式設計師jpamybatisJava程式語言

前言

JPA 和 Mybatis 的爭論由來已久,還記得在 2 年前我就在 spring4all 社群就兩者孰優孰劣的話題發表了觀點,我當時是力挺 JPA 的,這當然跟自己對 JPA 熟悉程度有關,但也有深層次的原因,便是 JPA 的設計理念契合了領域驅動設計的思想,可以很好地指導我們設計資料庫互動介面。這兩年工作中,逐漸接觸了一些使用 Mybatis 的專案,也對其有了一定新的認知。都說認知是一個螺旋上升的過程,隨著經驗的累積,人們會輕易推翻過去,到了兩年後的今天,我也有了新的觀點。本文不是為了告訴你 JPA 和 Mybatis 到底誰更好,而是嘗試求同存異,甚至是在專案中同時使用 JPA 和 Mybatis。什麼?要同時使用兩個 ORM 框架,有這個必要嗎?別急著吐槽我,希望看完本文後,你也可以考慮在某些場合下同時使用這兩個框架。

ps. 本文討論的 JPA 特指 spring-data-jpa。

建模

@Entity
@Table(name = "t_order")
public class Order {
  
  @Id
  private String oid;
  
  @Embedded
  private CustomerVo customer;
  
  @OneToMany(cascade = {CascadeType.ALL}, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "order")
  private
List<OrderItem> orderItems; }

JPA 最大的特點是 sqlless,如上述的實體定義,便將資料庫的表和 Java 中的型別關聯起來了,JPA 可以做到根據 @Entity 註解,自動建立表結構;基於這個實體實現的 Repository 介面,又使得 JPA 使用者可以很方便地實現資料的 CRUD。所以,使用 JPA 的專案,人們很少會提到”資料庫設計“,人們更關心的是領域建模,而不是資料建模。

<generatorConfiguration>
    <context id="my" targetRuntime=
"MyBatis3"> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="" userId="" password=""/> <javaModelGenerator targetPackage="" targetProject="" /> <sqlMapGenerator targetPackage="" targetProject="" /> <javaClientGenerator targetPackage="moe.cnkirito.demo.mapper" /> <table tableName="t_order" domainObjectName="Order" /> </context> </generatorConfiguration>

Mybatis 使用者更多使用的是逆向工程,例如 mybatis-generator 外掛根據如上的 xml 配置,便可以直接將表結構轉譯成 mapper 檔案和實體檔案。

code first 和 table first 從結果來看是沒有區別的,差異的是過程,所以設計良好的系統,並不會僅僅因為這個差異而高下立判,但從指導性來看,無疑設計系統時,更應該考慮的是實體和實體,實體和值物件的關聯,領域邊界的劃分,而不是首先著眼於資料庫表結構的設計。

建模角度來看,JPA 的領域建模思想更勝一籌。

資料更新
聊資料庫自然離不開 CRUD,先來看增刪改這些資料更新操作,來看看兩個框架一般的習慣是什麼。

JPA 推崇的資料更新只有一種正規化,分成三步:

先 findOne 對映成實體
記憶體內修改實體
實體整體 save
你可能會反駁我說,@Query 也存在 nativeQuery 和 JPQL 的用法,但這並不是主流用法。JPA 特別強調”整體 save“的思想,這與領域驅動設計所強調的有狀態密不可分,即其認為,修改不應該是針對於某一個欄位:”update table set a=b where colomonA=xx“ ,而應該反映成實體的變化,save 則代表了實體狀態最終的持久化。

先 find 後 save 顯然也適用於 Mybatis,而 Mybatis 的靈活性,使得其資料更新方式更加地百花齊放。路人甲可以認為 JPA 墨守成規不懂變通,認為 Mybatis 不羈放縱愛自由;路人乙也可以認為 JPA 格式規範易維護,Mybatis 不成方圓。這點不多加評判,留後人說。

從個人習慣來說,我還是偏愛先 find 後整體 save 這種習慣的,不是說這是 JPA 的專利,Mybatis 不具備;而是 JPA 的強制性,讓我有了這個習慣。

資料更新角度來看,JPA 強制使用 find+save,mybatis 也可以做到這一點,勝者:無。

資料查詢
JPA 提供的查詢方式主要分為兩種

簡單查詢:findBy + 屬性名
複雜查詢:JpaSpecificationExecutor
簡單查詢在一些簡單的業務場景下提供了非常大的便捷性,findBy + 屬性名可以自動轉譯成 sql,試問如果可以少寫程式碼,有誰不願意呢?

複雜查詢則是 JPA 為了解決複雜的查詢場景,提供的解決方案,硬是把資料庫的一些聚合函式,連線操作,轉換成了 Java 的方法,雖然做到了 sqlless,但寫出來的程式碼又臭又長,也不見得有多麼的易讀易維護。這算是我最不喜歡 JPA 的一個地方了,但要解決複雜查詢,又別無他法。

而 Mybatis 可以執行任意的查詢 sql,靈活性是 JPA 比不了的。資料庫小白搜尋的最多的兩個問題:

資料庫分頁怎麼做
條件查詢怎麼做

Mybatis 都可以輕鬆的解決。

千萬不要否認複雜查詢:如聚合查詢、Join 查詢的場景。令一個 JPA 使用者抓狂的最簡單方式,就是給他一個複雜查詢的 case。

select a,b,c,sum(a) where a=xx and d=xx group by a,b,c;

來吧,展示。可能 JPA 的確可以完成上述 sql 的轉義,但要知道不是所有開發都是 JPA 專家,沒人關心你用 JPA 解決了多麼複雜的查詢語句,更多的人關心地是,能不能下班前把這個複雜查詢搞定,早點回家。

在回到複雜資料查詢需求本身的來分析下。我們假設需求是合理的,畢竟專案的複雜性難以估計,可能有 1000 個數據查詢需求 JPA 都可以很方便的實現,但就是有那麼 10 幾個複雜查詢 JPA hold 不住。這個時候你只能乖乖地去寫 sql 了,如果這個時候又出現一個條件查詢的場景,出現了 if else 意味著連 @Query 都用不了,完全退化成了 JdbcTemplate 的時代。

那為什麼不使用 Mybatis 呢?Mybatis 使用者從來沒有糾結過複雜查詢,它簡直就是為之而生的。

如今很多 Mybatis 的外掛,也可以幫助使用者快速的生成基礎方法,雖然仍然需要寫 sql,但是這對於開發者來說,並不是一件難事。

不要質疑高併發下,JOIN 操作和聚合函式存在的可能性,資料查詢場景下,Mybatis 完勝。

效能
本質上 ORM 框架並沒有效能的區分度,因為最終都是轉換成 sql 交給資料庫引擎去執行,ORM 層面那層效能損耗幾乎可以忽略不計。

但從實際出發,Mybatis 提供給了開發者更高的 sql 自由度,所以在一些需要 sql 調優的場景下會更加靈活。

可維護性
前面我們提到 JPA 相比 Mybatis 喪失了 sql 的自由度,凡事必有 trade off,從另一個層面上來看,其提供了高層次的抽象,嘗試用統一的模型去解決資料層面的問題。sqlless 同時也遮蔽了資料庫的實現,遮蔽了資料庫高低版本的相容性問題,這對可能存在的資料庫遷移以及資料庫升級提供了很大的便捷性。

同時使用兩者
其他細節我就不做分析了,相信還有很多點可以拿過來做對比,但我相信主要的點上文都應該有所提及了。進行以上維度的對比並不是我寫這篇文章的初衷,更多地是想從實際開發角度出發,為大家使用這兩個框架提供一些參考建議。

在大多數場景下,我習慣使用 JPA,例如設計領域物件時,得益於 JPA 的正向模型,我會優先考慮實體和值物件的關聯性以及領域上下文的邊界,而不用過多關注如何去設計表結構;在增刪改和簡單查詢場景下,JPA 提供的 API 已經是刻在我 DNA 裡面的正規化了,使用起來非常的舒服。

在複雜查詢場景下,例如

包含不存在領域關聯的 join 查詢
包含多個聚合函式的複雜查詢
其他 JPA 較難實現的查詢
我會選擇使用 Mybatis,有點將 Mybatis 當做資料庫檢視生成器的意味。堅定不移的 JPA 擁躉者可能會質疑這些場景的存在的真實性,會質疑是不是設計的漏洞,但按照經驗來看,哪怕是短期方案,這些場景也是客觀存在的,所以聽我一言,嘗試擁抱一下 Mybatis 吧。

隨著各類儲存中介軟體的流行,例如 mongodb、ES,取代了資料庫的一部分地位,重新思考下,本質上都是在用專業的工具解決特定場景的問題,最終目的都是為了解放生產力。資料庫作為最古老,最基礎的儲存元件,的確承載了很多它本不應該承受的東西,那又何必讓一個工具或者一個框架成為限制我們想象力的溝壑呢?

兩個框架其實都不重,在 springboot 的加持下,引入幾行配置就可以實現兩者共存了。

我自己在最近的專案中便同時使用了兩者,遵循的便是本文前面聊到的這些規範,我也推薦給你,不妨試試。

零基礎學習Java,推薦個人Java學習園地