同時支持mybatis,hibernate等技術的通用持久層實現思路
java開源平台的技術框架非常豐富,但是開源平台上的權限管理、會員管理之類的純業務模塊往往與某種技術框架耦合在一起,比如與mybatis,hibernate等持久層技術耦合。一旦你選定了某個現成的業務組件,就必須接受他對應的持久層框架。比如如果你選擇用知名開源框架jeesite做自己的小型web應用係統,你在用它的權限、cms業務模塊的時候,就必須用mybaits做持久化框架,而不能用hibernate或spring jpa之類的,這對於隻考慮用hibernate而不打算用mybatis的團隊來說,不得不得放棄這個框架。所以,我在想,如果能有一些通用的業務組件,可以同時支持mybatis,hibernate等不同的持久層技術,可能具有更好的通用性和可移植性。
在開源平台上,確實有這類似需求的解決方案,比如開源中國上的Uncode-DAL項目,就支持同時支持mybatis、spring jdbc、hibernate等ORM框架。但遺憾的是,它隻支持單表操作,不支持多表關聯之類的功能。實事上,在待久層框架中,處理多表關聯確實是一個比較複雜的問題。
我經過一番摸索,找到了一種感覺還過得去的解決方案,並已經用到了自己的jad項目中。這裏先給出實現的思路和原理,以後jad項目開源後,再將這個框架一起開源。
功能需求
既然稱之為通用持久層,那麼需要實現的功能至少有以下幾點:
1、需要同時支持不同的待久層,至少需要同時支持mybatis、hibernate和spring jpa。
也就是說,通過這個框架實現的業務組件,可以在不修改任何業務代碼的基礎上,可靈活的在mybatis、hibernate和spring jpa之間任意切換。
2、支持多表關聯,至少需要支持一對多。
3、能自動處理不同數據庫之間的方言,比如分頁。
大致實現思路
考慮到不同的持久層框架,對OR映射,數據訪問等方式迥異。要做一個他們之間通用的框架,必須在它們之間找到一個共同的規範,讓它們同時遵守這個規範,就基本上能達到通用的目地。
似乎所有對數據庫的訪問,都是通過sql語問來實現的,隻不過hibernate等比較重量級的持久化框架還提供一些比如hql之類的語句來訪問,然而,這種hql最終還是被框架解釋成sql語句被傳送到數據庫。對於查詢語句,在數據庫返回數據後,大部分框架都會把數據庫的數據轉換成相應的實體對像(即使它是傳統的關係型數據庫),這個過程就是所調的OR映射。隻不過不同的框架,對OR映射的實現機製有所不同。
對於mybatis來說,需要開發人員自己維護對像屬性與表字段之間的關係,它並沒有實現完整的OR映射,但是它有比較完善動態sql引擎,用來實現動態拚接sql訪問數據庫。而hibernate非常完美的實現了OR 映射並對JPA規範都有很好的支持,而且還提供了一個麵象對象的hql查詢語言,大大方便了開發人員。spring jpa本身並沒有實現數據訪問邏輯,它隻不過在JPA規範的基礎上進行了一些擴展,采用一些大家都能接受的編碼規範來精簡編碼工作。程序員甚至隻要寫一些簡單的操作數據的Dao接口而無需任何實現就可以通過spring強大的IOC機製自動生成Dao的代理實現類來訪問數據庫。而且,Spring jpa底層並沒有實現訪問數據庫的具體邏輯,對於數據庫操作的具體邏輯,它最終還是委托給了比如Hibernate之類的框架作為一個持久化提供者的角色來實現數據訪問(Spring jpa默認的持久化提供者就是Hibernate,它支持通過配置,采用別的持久化提供者)。所以,spring jpa的本質隻不過是對現有的持久化方案做擴展,以提高開發效率而已。
通過以上分析,可以整理出一個大致的實現通用的持久化思路,就是統一的使用sql訪問數據庫,並實現統一的OR映射,把這兩點作為它們共同的規約。那麼問題來了,首先,如何不依賴於特定持久化框架來生成訪問數據庫的sql語句?其次,對於查詢,如何把關係型數轉換為對象?如果遇到多表關聯怎麼辦?難點匯總如下:
難點匯總與解決方案
1、如何不依賴於特定持久化框架來生成sql語句
對於普通的insert,update,delete語句,開發人員可以隨意寫sql語句,直接交給底層實現就行了。但是對於查詢,考慮到需要將查詢結果映射成對像,那麼就不通隨意寫了,需要符合某些規範。
因為hibernate有比較完整的OR實現,通過麵向對象的hql語言就可以操作數據庫,程員無需寫sql,同樣spring jpa也提供了類似的jql語句來進行操作,但mybatis就需要開發人員自己寫sql了。對於前兩個框架,他們都實現了jpa規範,可以通過jpa注解來做到實體屬性與表數據實段之間的映射。基於此,我在想,如果mybatis也能實現jpa規範,哪怕隻是部分實現也好。要麼,我們自己寫實現這個的邏輯,參考jpa規範,我們自己用一些諸如Column之類的注解來標識實現字段,通過反射的方式來構建對應的查詢sql。嗯,這確實是一個不錯的主意。
2、生成sql語句的過程如何解決表間關聯
在不依賴於特定持久化框架來生成sql語句的過程,難免會碰到多表關聯的情況,特別在處理查詢sql的查詢結果時,表間關聯的查詢比較普遍。上述第一點問題的解決方案中,通過返射來處理這些關聯時,稍微複雜一點。
實事上,對於所有持久層技術來說,表間關聯都比較難處理。在一些比較小眾的對常用持久層框架作改造或擴展的開發者們往往都會避開多表關聯,隻考慮對單表的持久化功能擴展。而遇到多表關聯時,依舊采用原始依賴的持久化框架來實現。比如號稱同時支持mybatis、spring jdbc、hibernate等ORM框架的Uncode-DAL框架,它也就隻支持單表操作而已,再比如對mybatis擴展的比較好的mybatis-plus項目,它也就隻擴展了單表操作,對於多表關聯時,還是要自己用原始的mybatis api來做關聯映射。
在JPA規範中,處理表間關聯時,可以采用一些諸如OneToMany和ManyToOne之類的注解來處理這些關係。所以,在不依賴於特定持久化框架來生成sql語句時,我們同樣可以自己寫代碼來處理ManyToOne之類的注解,把這些注解翻譯成我們自己的查詢sql,隻不過有一點小複雜而已,但致少是一種思路。如果實在處理不了,可以隻處理ManyToOne,把它翻譯成left join語句。
2、如何處理查詢語句的返回結果
對於數據庫返回的查詢結果,如何轉換對像?轉換過程中如何處理關聯?這確實是一個比較難處理的問題。我們都可以參考mybatis的實現方式,通過在構建查詢sql的過程中,把sql中查詢字段用類屬性名字作為別名,這樣mybatis就會自己轉換成對像。對於hibernate來說,也可以通過寫sql的方式,然後參考hibernate中的基於別名的結果集轉換器AliasedTupleSubsetResultTransformer的實現機製來做到轉換結果。如果要處理關聯,也可以參考mybatis的方式,在別名點用一些點號(".")來處理關聯列。而用hibernate時,我們也可以自己寫一個結果集處理器,從AliasedTupleSubsetResultTransformer類繼承,再在自己的處理器按照別名中的點號來自己實現關聯結果的轉換。這個過程可能有點複雜,但肯定是行得通的,因為我已經實現了。對於spring jpa,因為沒有比較好的結果集處理方案,但是考慮到spring jpa底層,本身也可以依賴於hibernate作為持久化提供者。在這種情況下,我們對查詢結果的處理,可以不用spring jpa的api而是退回來,使用原始的hibernate,采用跟hibernate一樣的處理方式來處理結果集映射。
如何集成
通過以上分析,我們完全可以不依賴於mybatis和hibernate等特定框架而隻采用jpa規範中的注解配合返射,就可以生成自己的sql語句,而且在生成sql語句的過程中,借鑒了mybatis中的按照別名的方式自動映射的機製,生成了方便處理結果集的相對規範的查詢語句。
有了上述基礎,做一個通用的框架就不難了。基於不重複造輪子的原則,參考spring jpa的架構思路。我們可以采用適配器的設計模式,同spring jpa一樣把操作數據庫的具體邏輯交給mybatis或hibernate等具體的提供方來實現(實事上,我們做的事情,就是spring jpa做的事情,隻不過我們把跟本不支持jpa規範的mybatis也考慮進來了,通用性更強)。
這樣,在使用通過這個通用框架的做出來的業務組件時,如果項目用的是mybatis,就通過配置,把操作數據的具體邏輯委托給mybaits實現,在用hibernate的項目裏,就把這些邏輯委托給hibernate來做(可以把操作數據庫的動作分為兩大類,一類是insert,update,delete執行語句,另一類是需要映射結果的select語句)。這樣,就實現了無需修改業務組件的代碼,而通用於mybatis,hibernate等任何持久化化框架的項目中。
值得一提的是,在集成mybatis的時候,如何自己處理sql中的問題參數占位符,如何分頁等,這都需要自己寫mybatis插件來實現。另外還有其它的種種細節問題,這裏就不多說了。本人已經按照這個思路實現了一個同時支持mybatis,hibernate和spring jpa並且支持一對多映射的通用框架,準備測試好後開源出來,有興趣的朋友們可以掃以下二維碼關注我的原創公眾已獲取最新信息。
最後更新:2017-04-08 03:32:01