天天看點

放棄 MyBatis、JPA,我最終選擇了 JDBC Template…

因為項目需要選擇資料持久化架構,看了一下主要幾個流行的和不流行的架構,對于複雜業務系統,最終的結論是,JOOQ是總體上最好的,可惜不是完全免費,最終選擇JDBC Template。

Hibernate和Mybatis是使用最多的兩個主流架構,而JOOQ、Ebean等小衆架構則知道的人不多,但也有很多獨特的優點;而JPA則是一組Java持久層Api的規範,Spring Data JPA是JPA Repository的實作,本來和Hibernate、Mybatis、JOOQ之類的架構不在同一個層次上,但引入Spring Data JPA之類架構之後,我們會直接使用JPA的API查詢更新資料庫,就像我們使用Mybatis一樣,是以這裡也把JPA和其他架構放在一起進行比較。

同樣,JDBC和其他架構也在同一層次,位于所有持久架構的底層,但我們有時候也會直接在項目中使用JDBC,而Spring JDBC Template部分消除了使用JDBC的繁瑣細節,降低了使用成本,使得我們更加願意在項目中直接使用JDBC。

一、SQL封裝和性能

在使用Hibernate的時候,我們查詢的是POJO實體類,而不再是資料庫的表,例如hql語句 select count(*) from User,裡面的User是一個Java類,而不是資料庫表User。這符合ORM最初的理想,ORM認為Java程式員使用OO的思維方式,和關系資料庫的思維方式差距巨大,為了填補對象和關系思維方式的鴻溝,必須做一個對象到關系的映射,然後在Java的對象世界中,程式員可以使用純的對象的思維方式,查詢POJO對象,查詢條件是對象屬性,不再需要有任何表、字段等關系的概念,這樣java程式員就更容易做持久層的操作。

JPA可以視為Hibernate的兒子,也繼承了這個思路,把SQL徹底封裝起來,讓Java程式員看不到關系的概念,用純的面向對象思想,重新創造一個新的查詢語言代替sql,比如hql,還有JPQL等。支援JPA的架構,例如Ebean都屬于這種類型的架構。

但封裝SQL,使用另一種純的面向對象查詢語言代替sql,真的能夠讓程式員更容易實作持久層操作嗎?MyBatis的流行證明了事實并非如此,至少在大多數情況下,使用hql并不比使用sql簡單。首先,從很多角度上看,hql/JPQL等語言更加複雜和難以了解;其次就是性能上明顯降低,速度更慢,記憶體占用巨大,而且還不好優化。最為惱火的是,當關系的概念被替換為對象的概念之後,查詢語言的靈活性變得很差,表達能力也比sql弱很多。寫查詢語句的時候受到各種各樣的限制,一個典型的例子就是多表關聯查詢。

不管是hibernate還是jpa,表之間的連接配接查詢,被映射為實體類之間的關聯關系,這樣,如果兩個實體類之間沒有(實作)關聯關系,你就不能把兩個實體(或者表)join起來查詢。這是很惱火的事情,因為我們很多時候并不需要顯式定義兩個實體類之間的關聯關系就可以實作業務邏輯,如果使用hql,隻是為了join我們就必須在兩個實體類之間添加代碼,而且還不能逆向工程,如果表裡面沒有定義外鍵限制的話,逆向工程會把我們添加的關聯代碼抹掉。

MyBatis則是另外一種類型的持久化架構,它沒有封裝SQL也沒有建立一種新的面相對象的查詢語言,而是直接使用SQL作為查詢語言,隻是把結果填入POJO對象而已。使用sql并不比hql和JPQL困難,查詢速度快,可以靈活使用任意複雜的查詢隻要資料庫支援。從SQL封裝角度上看,MyBatis比Hibernate和JPA成功,SQL本不該被封裝和隐藏,讓Java程式員使用SQL既不麻煩也更容易學習和上手,這應該是MyBatis流行起來的重要原因。

輕量級持久層架構JOOQ也和MyBatis一樣,直接使用SQL作為查詢語言,比起MyBatis,JOOQ雖然知名度要低得多,但JOOQ不但和MyBatis一樣可以利用SQL的靈活性和高效率,通過逆向工程,JOOQ還可以用Java代碼來編寫SQL語句,利用IDE的代碼自動補全功能,自動提示表名和字段名,減少程式員記憶負擔,還可以在中繼資料發生變化時發生編譯錯誤,提示程式員修改相應的SQL語句。

Ebean作為一種基于JPA的架構,它也使用JPQL語言進行查詢,多數情況下會讓人很惱火。但據說Ebean不排斥SQL,可以直接用SQL查詢,也可以用類似JOOQ的DSL方式在代碼中構造SQL語句(還是JPQL語句?),但沒用過Ebean,是以具體細節不清楚。

JDBC Template就不用說了,它根本沒做ORM,當然是純SQL查詢。利用Spring架構,可以把JDBC Template和JPA結合起來使用,在JPA不好查詢的地方,或者效率低不好優化的地方使用JDBC,緩解了Hibernate/JPA封裝SQL造成的麻煩,但我仍沒看到任何封裝SQL的必要性,除了給程式員帶來一大堆麻煩和學習負擔之外,沒有太明顯的好處。

二、DSL和變化适應性

為了實作複雜的業務邏輯,不論是用SQL還是hql或者JPQL,我們都不得不寫很多簡單的或者複雜的查詢語句,ORM無法減少這部分工作,最多是用另一種面向對象風格的語言去表達查詢需求,如前所述,用面向對象風格的語言不見得比SQL更容易。通常業務系統中會有很多表,每個表都有很多字段,即便是編寫最簡單的查詢語句也不是一件容易的事情,需要記住資料庫中有哪些表,有哪些字段,記住有哪些函數等。寫查詢語句很多時候成為一件頭疼的事情。

QueryDSL、JOOQ、Ebean甚至MyBatis和JPA都設計一些特性,幫助開發人員編寫查詢語句,有人稱之為“DSL風格資料庫程式設計”。最早實作這類功能的可能是QueryDSL,把資料庫的表結構逆向工程為java的類,然後可以讓java程式員能夠用java的文法構造出一個複雜的查詢語句,利用IDE的代碼自動補全功能,可以自動提示表名、字段名、查詢語句的關鍵字等,很成功的簡化了查詢語句的編寫,免除了程式員記憶各種名字、函數和關鍵字的負擔。

QueryDSL有很多版本,但用得多的是QueryDSL JPA,可以幫助開發人員編寫JPQL語句,如前所述,JPQL語句有很多局限不如SQL靈活高效。後來的JOOQ和Ebean,基本上繼承了QueryDSL的思路,Ebean基本上還是JPA風格的ORM架構,雖然也支援SQL,但不清楚其DSL特性是否支援SQL語句編寫,在官網上看到的例子都是用于構造JPQL語句。

這裡面最成功的應該是JOOQ,和QueryDSL不同,JOOQ的DSL程式設計是幫助開發人員編寫SQL語句,抛棄累贅的ORM概念,JOOQ這個功能非常輕小,非常容易學習和使用,同時性能也非常好,不像QueryDSL和Ebean,需要了解複雜的JPA概念和各種奇異的限制,JOOQ編寫的就是普通的SQL語句,隻是把查詢結果填充到實體類中(嚴格說JOOQ沒有實體類,隻是自動生成的Record對象),JOOQ甚至不一定要把結果轉換為實體類,可以讓開發人員按照字段取得結果的值,相對于JDBC,JOOQ會把結果值轉換為合适的Java類型,用起來比JDBC更簡單。

傳統主流的架構對DSL風格支援得很少,Hibernate裡面基本上沒有看到有這方面的特性。MyBatis提供了"SQL語句建構器"來幫助開發人員構造SQL語句,但和QueryDSL/JOOQ/Ebean差很多,不能提示表名和字段名,文法也顯得累贅不像SQL。

JPA給人的印象是複雜難懂,它的MetaModel Api繼承了特點,MetaModel API+Criteria API,再配合Hibernate JPA 2 Metamodel Generator,讓人有點QueryDSL JPA的感覺,隻是繞了一個大大的彎,疊加了好幾層技術,最後勉強實作了QueryDSL JPA的簡單易懂的功能。很多人不推薦JPA+QueryDSL的用法,而是推薦JPA MetaModel API+Criteria API+Hibernate JPA 2 Metamodel Generator的用法,讓人很難了解,也許是因為這個方案是純的标準的JPA方案。

資料庫DSL程式設計的另一個主要賣點是變化适應性強,資料庫表結構在開發過程中通常會頻繁發生變化,傳統的非DSL程式設計,字段名隻是一個字元串,如果字段名或者類型改變之後,查詢語句沒有相應修改,編譯不會出錯,也容易被開發人員忽略,是bug的一個主要來源。DSL程式設計裡面,字段被逆向工程為一個java類的屬性,資料庫結構改變之後,作為java代碼一部分的查詢語句會發生編譯錯誤,提示開發人員進行修改,可以減少大量bug,減輕測試的負擔,提高軟體的可靠性和品質。

三、跨資料庫移植

Hibernate和JPA使用hql和JPQL這類資料庫無關的中間語言描述查詢,可以在不同資料庫中無縫移植,移植到一個SQL有巨大差别的資料庫通常不需要修改代碼或者隻需要修改很少的代碼。Ebean如果不使用原生SQL,而是使用JPA的方式開發,也能在不同資料庫中平滑的移植。

MyBatis和JOOQ直接使用SQL,跨資料庫移植時都難免要修改SQL語句。這方面MyBatis比較差,隻有一個動态SQL提供的特性,對于不同的資料庫編寫不同的sql語句。

JOOQ雖然無法像Hibernate和JPA那樣無縫移植,但比MyBatis好很多。JOOQ的DSL很大一部分是通用的,例如分頁查詢中,Mysql的limit/offset關鍵字是很友善的描述方式,但Oracle和SQLServer的SQL不支援,如果我們用JOOQ的DSL的limit和offset方法構造SQL語句,不修改移植到不支援limit/offset的Oracle和SQLServer上,我們會發現這些語句還能正常使用,因為JOOQ會把limit/offset轉換成等價的目标資料庫的SQL語句。JOOQ根據目标資料庫轉換SQL語句的特性,使得在不同資料庫之間移植的時候,隻需要修改很少的代碼,明顯優于MyBatis。

JDBC Template應該最差,隻能盡量使用标準sql語句來減少移植工作量。

四、安全性

一般來說,拼接查詢語句都會有安全隐患,容易被sql注入攻擊。不論是jdbc,還是hql/JPQL,隻要使用拼接的查詢語句都是不安全的。對于JDBC來說,使用參數化的sql語句代替拼接,可以解決問題。而JPA則應該使用Criteria API解決這個問題。

對于JOOQ之類的DSL風格架構,最終會被render為參數化的sql,天生免疫sql注入攻擊。Ebean也支援DSL方式程式設計,也同樣免疫sql注入攻擊。

這是因為DSL風格程式設計參數化查詢比拼接字元串查詢更簡單,沒人會拼接字元串。而jdbc/hql/JPQL拼接字元串有時候比參數化查詢更簡單,特别是jdbc,很多人會偷懶使用不安全的方式。

五、JOOQ的失敗之處

可能大部分人會不同意,雖然Hibernate、JPA仍然大行其道,是最主流的持久化架構,但其實這種封裝SQL的純正ORM已經過時,效益低于使用它們的代價,應該淘汰了。MyBatis雖然有很多優點,但它的優點JOOQ基本上都有,而且多數還更好。MyBatis最大的缺點是難以避免寫xml檔案,xml檔案編寫困難,容易出錯,還不容易查找錯誤。相對于JOOQ,MyBatis在多數情況下沒有任何優勢。

Ebean同時具有很多不同架構的優點,但它是基于JPA的,難免有JPA的各種限制,這是緻命的缺點。

JOOQ這個極端輕量級的架構技術上是最完美的,突然有一天幾個Web系統同時崩了,最後發現是JOOQ試用期過期了,這是JOOQ的失敗之處,它不是完全免費的,隻是對MySql之類的開源資料庫免費。

最終,我決定選擇JDBC Template。