魚(yú)和熊掌兼得:同時(shí)使用 JPA 和 Mybatis
前言
JPA 和 Mybatis 的爭(zhēng)論由來(lái)已久,還記得在 2 年前我就在 spring4all 社區(qū)就兩者孰優(yōu)孰劣的話(huà)題發(fā)表了觀點(diǎn),我當(dāng)時(shí)是力挺 JPA 的,這當(dāng)然跟自己對(duì) JPA 熟悉程度有關(guān),但也有深層次的原因,便是 JPA 的設(shè)計(jì)理念契合了領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的思想,可以很好地指導(dǎo)我們?cè)O(shè)計(jì)數(shù)據(jù)庫(kù)交互接口。這兩年工作中,逐漸接觸了一些使用 Mybatis 的項(xiàng)目,也對(duì)其有了一定新的認(rèn)知。都說(shuō)認(rèn)知是一個(gè)螺旋上升的過(guò)程,隨著經(jīng)驗(yàn)的累積,人們會(huì)輕易推翻過(guò)去,到了兩年后的今天,我也有了新的觀點(diǎn)。本文不是為了告訴你 JPA 和 Mybatis 到底誰(shuí)更好,而是嘗試求同存異,甚至是在項(xiàng)目中同時(shí)使用 JPA 和 Mybatis。什么?要同時(shí)使用兩個(gè) ORM 框架,有這個(gè)必要嗎?別急著吐槽我,希望看完本文后,你也可以考慮在某些場(chǎng)合下同時(shí)使用這兩個(gè)框架。
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 ListorderItems;
}
JPA 最大的特點(diǎn)是 sqlless,如上述的實(shí)體定義,便將數(shù)據(jù)庫(kù)的表和 Java 中的類(lèi)型關(guān)聯(lián)起來(lái)了,JPA 可以做到根據(jù) @Entity 注解,自動(dòng)創(chuàng)建表結(jié)構(gòu);基于這個(gè)實(shí)體實(shí)現(xiàn)的 Repository 接口,又使得 JPA 用戶(hù)可以很方便地實(shí)現(xiàn)數(shù)據(jù)的 CRUD。所以,使用 JPA 的項(xiàng)目,人們很少會(huì)提到”數(shù)據(jù)庫(kù)設(shè)計(jì)“,人們更關(guān)心的是領(lǐng)域建模,而不是數(shù)據(jù)建模。
<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 用戶(hù)更多使用的是逆向工程,例如 mybatis-generator 插件根據(jù)如上的 xml 配置,便可以直接將表結(jié)構(gòu)轉(zhuǎn)譯成 mapper 文件和實(shí)體文件。
code first 和 table first 從結(jié)果來(lái)看是沒(méi)有區(qū)別的,差異的是過(guò)程,所以設(shè)計(jì)良好的系統(tǒng),并不會(huì)僅僅因?yàn)檫@個(gè)差異而高下立判,但從指導(dǎo)性來(lái)看,無(wú)疑設(shè)計(jì)系統(tǒng)時(shí),更應(yīng)該考慮的是實(shí)體和實(shí)體,實(shí)體和值對(duì)象的關(guān)聯(lián),領(lǐng)域邊界的劃分,而不是首先著眼于數(shù)據(jù)庫(kù)表結(jié)構(gòu)的設(shè)計(jì)。
建模角度來(lái)看,JPA 的領(lǐng)域建模思想更勝一籌。
數(shù)據(jù)更新
聊數(shù)據(jù)庫(kù)自然離不開(kāi) CRUD,先來(lái)看增刪改這些數(shù)據(jù)更新操作,來(lái)看看兩個(gè)框架一般的習(xí)慣是什么。
JPA 推崇的數(shù)據(jù)更新只有一種范式,分成三步:
-
先 findOne 映射成實(shí)體
-
內(nèi)存內(nèi)修改實(shí)體
-
實(shí)體整體 save
你可能會(huì)反駁我說(shuō),@Query 也存在 nativeQuery 和 JPQL 的用法,但這并不是主流用法。JPA 特別強(qiáng)調(diào)”整體 save“的思想,這與領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)所強(qiáng)調(diào)的有狀態(tài)密不可分,即其認(rèn)為,修改不應(yīng)該是針對(duì)于某一個(gè)字段:”update table set a=b where colomonA=xx“ ,而應(yīng)該反映成實(shí)體的變化,save 則代表了實(shí)體狀態(tài)最終的持久化。
先 find 后 save 顯然也適用于 Mybatis,而 Mybatis 的靈活性,使得其數(shù)據(jù)更新方式更加地百花齊放。路人甲可以認(rèn)為 JPA 墨守成規(guī)不懂變通,認(rèn)為 Mybatis 不羈放縱愛(ài)自由;路人乙也可以認(rèn)為 JPA 格式規(guī)范易維護(hù),Mybatis 不成方圓。這點(diǎn)不多加評(píng)判,留后人說(shuō)。
從個(gè)人習(xí)慣來(lái)說(shuō),我還是偏愛(ài)先 find 后整體 save 這種習(xí)慣的,不是說(shuō)這是 JPA 的專(zhuān)利,Mybatis 不具備;而是 JPA 的強(qiáng)制性,讓我有了這個(gè)習(xí)慣。
數(shù)據(jù)更新角度來(lái)看,JPA 強(qiáng)制使用 find+save,mybatis 也可以做到這一點(diǎn),勝者:無(wú)。
數(shù)據(jù)查詢(xún)
JPA 提供的查詢(xún)方式主要分為兩種
-
簡(jiǎn)單查詢(xún):findBy + 屬性名
-
復(fù)雜查詢(xún):JpaSpecificationExecutor
簡(jiǎn)單查詢(xún)?cè)谝恍┖?jiǎn)單的業(yè)務(wù)場(chǎng)景下提供了非常大的便捷性,findBy + 屬性名可以自動(dòng)轉(zhuǎn)譯成 sql,試問(wèn)如果可以少寫(xiě)代碼,有誰(shuí)不愿意呢?
復(fù)雜查詢(xún)則是 JPA 為了解決復(fù)雜的查詢(xún)場(chǎng)景,提供的解決方案,硬是把數(shù)據(jù)庫(kù)的一些聚合函數(shù),連接操作,轉(zhuǎn)換成了 Java 的方法,雖然做到了 sqlless,但寫(xiě)出來(lái)的代碼又臭又長(zhǎng),也不見(jiàn)得有多么的易讀易維護(hù)。這算是我最不喜歡 JPA 的一個(gè)地方了,但要解決復(fù)雜查詢(xún),又別無(wú)他法。
而 Mybatis 可以執(zhí)行任意的查詢(xún) sql,靈活性是 JPA 比不了的。數(shù)據(jù)庫(kù)小白搜索的最多的兩個(gè)問(wèn)題:
-
數(shù)據(jù)庫(kù)分頁(yè)怎么做
-
條件查詢(xún)?cè)趺醋?/span>
Mybatis 都可以輕松的解決。
千萬(wàn)不要否認(rèn)復(fù)雜查詢(xún):如聚合查詢(xún)、Join 查詢(xún)的場(chǎng)景。令一個(gè) JPA 用戶(hù)抓狂的最簡(jiǎn)單方式,就是給他一個(gè)復(fù)雜查詢(xún)的 case。
select a,b,c,sum(a) where a=xx and d=xx group by a,b,c;
來(lái)吧,展示??赡?JPA 的確可以完成上述 sql 的轉(zhuǎn)義,但要知道不是所有開(kāi)發(fā)都是 JPA 專(zhuān)家,沒(méi)人關(guān)心你用 JPA 解決了多么復(fù)雜的查詢(xún)語(yǔ)句,更多的人關(guān)心的是,能不能下班前把這個(gè)復(fù)雜查詢(xún)搞定,早點(diǎn)回家。
在回到復(fù)雜數(shù)據(jù)查詢(xún)需求本身的來(lái)分析下。我們假設(shè)需求是合理的,畢竟項(xiàng)目的復(fù)雜性難以估計(jì),可能有 1000 個(gè)數(shù)據(jù)查詢(xún)需求 JPA 都可以很方便的實(shí)現(xiàn),但就是有那么 10 幾個(gè)復(fù)雜查詢(xún) JPA hold 不住。這個(gè)時(shí)候你只能乖乖地去寫(xiě) sql 了,如果這個(gè)時(shí)候又出現(xiàn)一個(gè)條件查詢(xún)的場(chǎng)景,出現(xiàn)了 if else 意味著連 @Query 都用不了,完全退化成了 JdbcTemplate 的時(shí)代。
那為什么不使用 Mybatis 呢?Mybatis 使用者從來(lái)沒(méi)有糾結(jié)過(guò)復(fù)雜查詢(xún),它簡(jiǎn)直就是為之而生的。
如今很多 Mybatis 的插件,也可以幫助使用者快速的生成基礎(chǔ)方法,雖然仍然需要寫(xiě) sql,但是這對(duì)于開(kāi)發(fā)者來(lái)說(shuō),并不是一件難事。
不要質(zhì)疑高并發(fā)下,JOIN 操作和聚合函數(shù)存在的可能性,數(shù)據(jù)查詢(xún)場(chǎng)景下,Mybatis 完勝。
性能
本質(zhì)上 ORM 框架并沒(méi)有性能的區(qū)分度,因?yàn)樽罱K都是轉(zhuǎn)換成 sql 交給數(shù)據(jù)庫(kù)引擎去執(zhí)行,ORM 層面那層性能損耗幾乎可以忽略不計(jì)。
但從實(shí)際出發(fā),Mybatis 提供給了開(kāi)發(fā)者更高的 sql 自由度,所以在一些需要 sql 調(diào)優(yōu)的場(chǎng)景下會(huì)更加靈活。
可維護(hù)性
前面我們提到 JPA 相比 Mybatis 喪失了 sql 的自由度,凡事必有 trade off,從另一個(gè)層面上來(lái)看,其提供了高層次的抽象,嘗試用統(tǒng)一的模型去解決數(shù)據(jù)層面的問(wèn)題。sqlless 同時(shí)也屏蔽了數(shù)據(jù)庫(kù)的實(shí)現(xiàn),屏蔽了數(shù)據(jù)庫(kù)高低版本的兼容性問(wèn)題,這對(duì)可能存在的數(shù)據(jù)庫(kù)遷移以及數(shù)據(jù)庫(kù)升級(jí)提供了很大的便捷性。
同時(shí)使用兩者
其他細(xì)節(jié)我就不做分析了,相信還有很多點(diǎn)可以拿過(guò)來(lái)做對(duì)比,但我相信主要的點(diǎn)上文都應(yīng)該有所提及了。進(jìn)行以上維度的對(duì)比并不是我寫(xiě)這篇文章的初衷,更多地是想從實(shí)際開(kāi)發(fā)角度出發(fā),為大家使用這兩個(gè)框架提供一些參考建議。
在大多數(shù)場(chǎng)景下,我習(xí)慣使用 JPA,例如設(shè)計(jì)領(lǐng)域?qū)ο髸r(shí),得益于 JPA 的正向模型,我會(huì)優(yōu)先考慮實(shí)體和值對(duì)象的關(guān)聯(lián)性以及領(lǐng)域上下文的邊界,而不用過(guò)多關(guān)注如何去設(shè)計(jì)表結(jié)構(gòu);在增刪改和簡(jiǎn)單查詢(xún)場(chǎng)景下,JPA 提供的 API 已經(jīng)是刻在我 DNA 里面的范式了,使用起來(lái)非常的舒服。
在復(fù)雜查詢(xún)場(chǎng)景下,例如
-
包含不存在領(lǐng)域關(guān)聯(lián)的 join 查詢(xún)
-
包含多個(gè)聚合函數(shù)的復(fù)雜查詢(xún)
-
其他 JPA 較難實(shí)現(xiàn)的查詢(xún)
我會(huì)選擇使用 Mybatis,有點(diǎn)將 Mybatis 當(dāng)做數(shù)據(jù)庫(kù)視圖生成器的意味。堅(jiān)定不移的 JPA 擁躉者可能會(huì)質(zhì)疑這些場(chǎng)景的存在的真實(shí)性,會(huì)質(zhì)疑是不是設(shè)計(jì)的漏洞,但按照經(jīng)驗(yàn)來(lái)看,哪怕是短期方案,這些場(chǎng)景也是客觀存在的,所以聽(tīng)我一言,嘗試擁抱一下 Mybatis 吧。
隨著各類(lèi)存儲(chǔ)中間件的流行,例如 mongodb、ES,取代了數(shù)據(jù)庫(kù)的一部分地位,重新思考下,本質(zhì)上都是在用專(zhuān)業(yè)的工具解決特定場(chǎng)景的問(wèn)題,最終目的都是為了解放生產(chǎn)力。數(shù)據(jù)庫(kù)作為最古老,最基礎(chǔ)的存儲(chǔ)組件,的確承載了很多它本不應(yīng)該承受的東西,那又何必讓一個(gè)工具或者一個(gè)框架成為限制我們想象力的溝壑呢?
兩個(gè)框架其實(shí)都不重,在 springboot 的加持下,引入幾行配置就可以實(shí)現(xiàn)兩者共存了。
我自己在最近的項(xiàng)目中便同時(shí)使用了兩者,遵循的便是本文前面聊到的這些規(guī)范,我也推薦給你,不妨試試。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!