2015年8月2日 星期日

當JPA 遇到 JSON 偷懶是進步的動力



話說這篇壓了好久遲遲不敢放出來...不過想想放出來讓大家笑笑,讓大家少走點冤枉路也好~:P

上圖是一般系統開發常見的資料流程,不管是先設計Db schema 或是先設計 Object Model,到最後你可能都會遇到兩個問題:
  1. 怎麼把DB Table轉成Java物件
  2. 怎麼把Java物件轉成Json格式傳出去 (常見的就是RESTful API)


針對第一點比較常見的就是使用JPA (不管你底層用Hibernate還是EclipseLink..等),你說你要直接用寫SQL語法然後自己mapping物件?也...是可以啦...

而第二點呢?把Java 物件轉成JSON 格式,大家聽到的第一個想法一定就是使用轉換的Library 如Jackson 或是Gson,話說之前我有寫過一篇(Jackson Java JSON-processor vs Gson ),"感覺" Jackson的效能應該會比較好,所以一直想嘗試看看,但是後來真的用下去....才覺得非常痛苦,因為不太好用....

不過這都還好,真正最恐怖的是 JPA遇到JSON的時候,這才會讓人一個頭兩個大!因為 JPA 有幾個特性在設計Object model 時要特別注意:
  • Lazy loading
  • Bi-direction reference
但是這兩個特性一旦遇到嘗試把Object 序列化(Serialize)成 JSON 格式時,就會成了引爆點。

先針對Lazy loading 的問題,已經有人提供了以下模組可以使用Jackson-module-hibernate (阿但是我是用EclipseLink怎麼辦...囧?)

在網路上隨便Goolge 一下,就會發現很多人都會遇到這個問題:(沒辦法我們工程師就是想偷懶~Orz..),比較正確的解決方法就是多產生一個DTO (Data Transaction Object),把DAO (Data Access Object)產生的物件Wrapper 過後放到DTO,這樣就可以解決大部分的問題,不過如果你偷懶想要直接把DAO 拉出來的物件直接轉成JSON丟出去,那就得另外想辦法處理。

Jackson 這套Library 對於這幾個問題的處理其實一直在演進:

第一代:
在1.6版以前,Jackson 提供了個BiDirReference的方式嘗試來解決問題(範例程式:JSON - Jackson to the rescue ),而網路上最常看到的範例程式幾乎都是使用這種寫法。

第二代:
不過到了 2.x版以後 Jackson 提出了全新的解決方式 - ObjectIdentity 的方式,也就是當使用ObjectIdGenerators.IntSequenceGenerator.class時,Jackson 會把重複出現的物件用@tag id取代
這是為了解決Recursive 得問題。
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class,
                              property = "@tag") 


但是這只適合用在都是用Jackson Lib的和Java Client to Java Server,因為會長出許多Spec不該出現的"@id":1這種噁心的東西(會被其他工程師歐飛~)

{
  "@id" : 1,
  "name" : "Bob",
  "bestFriend" : {
     "@id" : 2,
     "name" : "Bill"
     "bestFriend" : 1
  }
}

很不幸的這種東西只能給Java 而且都是使用Jackson 相通lib 的程式共用,如果把這種東西丟出去,其他程式根本無法無法理解~

如果要把這個噁心的東西關掉,其中一個解法可以參考Jackson-databind 這個討論串
@JsonIdentityReference(alwaysAsId = true)

另外在stackoverflow 也有相關的文章 how to disable object reference when using jsonidentityinfo


第三代?

其實Jackson 也是有持需再針對這個問題提出更多種解法....但是仔細想想是不是我們把事情搞得太複雜了?最後還是回到根本,不要偷懶,多加一層DTO吧,不要直接把DAO的物件轉成Json丟出去比較好..



參考資料:

[1] Presentation Jackson 2.0





沒有留言 :