Hibernate performance

10種常見的Hibernate謬誤,正威脅著你的系統效能

陳膺傑 2018/12/29 15:50:42
9412

10種常見的Hibernate謬誤,正威脅著你的系統效能


簡介

Hibernate基於ORM的框架,透過物件對資料表進行相關操作,使我們更便利的進行開發,但,在我們不經意的使用下,其實潛藏著影響系統效能的重大危機!

作者

陳膺傑


前言

Hibernate基於ORM的框架,透過物件對資料表進行相關操作,使我們更便利的進行開發,但,在我們不經意的使用下,其實潛藏著影響系統效能的重大危機!   

Mistake 1:Use Eager Fetching

當透過以下Annotation @OneToMany, @ManyToOne, @ManyToMany and @OneToOne 定義資料表關連關係時,可額外指定FetchType來定義Hibernate需如何載入關連的資料表,如以下程式碼所示:   
 
若我們設定為FetchType.EAGER,當我們取得Author時,關連的所有Book也在同時一併取得,這意味著Hibernate 會主動進行額外的查詢,不管我們是否需要Book的資料,在沒注意到的情況下,可能造成數十個或數百個額外的查詢!
 
為避免以上的情況發生,需改用FetchType.LAZYBook將在商業邏輯中有需要用到的時候才載入,避免造成不必要的額外查詢。

Mistake 2: Ignore the Default FetchType of To-One Associations

使用Annotation設定任何to-one的關聯,當我們沒設定FetchType時,Hibernate採用預設的FetchTypeEAGER,雖然對系統來說只是一筆額外的資料載入,並不是多大的問題,但,如有需要一次載入多筆這個物件時,也會造成大量不必要的額外查詢。
 
為避免以上的情況發生,在設定任何to-one的關聯時,一定要記得設定FetchType.LAZY,避免Hibernate採用了預設的FetchType.EAGER而造成不必要的額外查詢。  
 

Mistake 3: Don’t Initialize Required Associations

當我們設定FetchType.LAZY來避免前兩個問題時,卻可能會造成另一個嚴重的問題,這問題出現在Hibernate一次取得n個Entity後,必須額外再進行n次的Query來初始化每個Entity上lazily fetched的關聯資料。
 
如上述的程式,當我們取得Author之後,呼叫了getBooks,如此一來,Hibernate得再進行額外的Query來取得所有關連的Book。
 
這是Hibernate熟為人知的N+1 問題,當然網路上也可找到很多解決的辦法,其中一個最簡單的方式就是在JPQL的語法中加入JOIN FETCH,如下圖所示:

Mistake 4: Select More Records Than You Need

一次取得太多資料也會造成系統效能的負擔,在JPQL及HQL的語法中並不能使用LIMIT來限制取得的資料數量,也不支援這樣的語法。
 
為避免一次取得太多不必要的資料,可改用其他作法來限制查詢的數量,如下圖所示:

Mistake 5: Don’t use Bind Parameters

在查詢語法中綁定參數提供我們以下的優點:
  1.方便使用與維護
  2.Hibernate會自動進行必要的轉換
  3.Hibernate會自動對字串進行過濾,可避免SQL Injection
 
雖然以上優點可能與效能無關,但卻可幫助我們產出更高效能的系統。
 
下圖為使用別名綁定參數的範例:

Mistake 6: Perform all Logic in Your Business Code

我們很自然的會將所有業務邏輯都放在Business layer,可以使用我們最熟悉的語言、libraries以及tools。
 
但有時候,資料庫是對大量資料進行邏輯處理的更好地方!我們可以在JPQL及SQL中,透過使用function,或是stored procedure來進行相關的邏輯處理。
 
在JPQL中可以使用標準函數:
 
使用JPA的函數功能,還可以調用特定資料庫的函數或是自定的資料庫函數:

Mistake 7: Call the flush Method Without a Reason

這是另一個很常見的錯誤,開發人員在存入新的資料或是更新現有資料後,叫用了EntityManager的flush方法,這會迫使Hibernate對所有管理的entities進行dirty check,並即時進行所需要的新增、更新或刪除的操作。因此阻礙了Hibernate所進行的內部優化,而拖慢了系統的效能。
 
內部優化指的像是Hibernate會將所有管理的entities存在persistence context當中,且嘗試盡可能的延遲寫入操作的執行,這允許Hibernate對同一個entities的多個更新操作合併為一段更新的SQL,透過JDBC批次處理多個相同的SQL,並避免執行當前Session中已取得的entity的重複SQL語法。
 
因此,應該避免呼叫任何的flush方法,除了在JPQL中的批量操作。

Mistake 8: Use Hibernate for Everything

Hibernate的ORM跟各種效能的優化使得大多數的CRUD用法上變得更加簡單而且有效率,這使得Hibernate成為大多數專案中熱門的選擇,但並不意味著它對各個專案都是一個很好的解決方案。
 
像有些系統中,需要使用非常複雜的語法來產出報表或統計時,或需要對大量資料進行寫入操作時,這些情況下就不適合使用Hibernate,但如果這些案例只是系統當中的一小部分,仍然還是可以使用Hibernate,所以應該先對專案進行評估,評估是否使用Hibernate。

Mistake 9: Update or Delete Huge Lists of Entities One by One

當我們需要大量更新或刪除資料時,可以透過一段UPDATE或DELETE的語法達成這樣的目的。
 
很不幸地,這對JPA跟Hibernate來說並不容易,每個Entity都有他的生命週期,如果我們想要更新或刪除多筆資料,必須先從資料庫將它們載入,然後對每個Entity進行操作,Hibernate會針對每個Entity產生必要的UPDATE或DELETE語法,所以Hibernate不會僅使用一段語法去更新1000筆資料,而是執行1001段語法。
 
幸好,我們還可以透過JPQL, native SQL或是Criteria query來達成這樣的目的。
 
只是這麼做,雖可以讓我們不透過Entity來對DB進行UPDATE或DELETE,但也相對的略過了Entity的生命週期,Hibernate的緩存機制無法針對這些資料進行任何更新。

Mistake 10: Use Entities for Read-Only Operations

JPA和Hibernate支援數種不同的映射,當我們所需要的只是資料表當中的部分欄位時,單獨只取出我們要的欄位內容,透過DTO projections會比使用Entity的速度來的更快,效能更好。
 
因此要適時選擇適當的作法,而不是一昧只透過Entity來取得資料。

總結

如同上述所列,有很多小地方是容易被忽略的,對我們的系統效能可能會造成莫大的影響,下次在使用Hibernate時,必須特別注意以上所列各項Mistake,避免為系統埋下地雷!
陳膺傑
BCC11B48EF9E160C5A64F2B44B0124C9
2019/03/05 11:54:39

Good summarization