天天看點

JPA2.1 中三個提升應用性能的新功能

<b>經常在網上看到開發者們抱怨 jpa 性能低下的文章或文章,但如果仔細檢視這些性能問題,常會發現導緻問題的根本原因大緻包括以下幾個:</b>

使用過多的 sql 查詢從資料庫中擷取所需的實體資訊,即我們常說的n+1查詢問題

逐個更新實體,而不是使用單條語句進行更新

使用 java 應用程式而非資料庫進行大量資料處理

JPA2.1 中三個提升應用性能的新功能

jpa提供了處理這類問題的方法,并給 jpa2.1 增加了一些額外功能,可以極大地提升性能表現,筆者将在本文中解釋如何利用 jpa2.1 的功能避免上述問題。

順便提一下,如果想了解java項目中更多的典型性能問題,可以參考筆者最近釋出的基于性能調查結果的深度報告,如果你在尋找 jpa 資源,點選此連結便可擷取jpa2.1特征的備忘清單。接下來我們來看看如何用jpa來解決現有的性能問題。

根據以往的經驗,使用過多的 sql 查詢擷取所要求的實體是導緻性能問題最普遍的原因。

即使是看起來最簡單的查詢,如果操作不當,也會觸發幾十次甚至上百次的 sql 查詢。而且,你在本節中可以看到,這類不當操作不一定會出現在查詢語句中,而可能隻是幾個配置不當的注解。是以,如果你覺得這個問題不會造成影響,請三思。

如果在你的項目中出現以下幾段代碼,你會怎麼想?

上面的代碼段會列印所有作者的姓名及其書名,看起來非常簡單,但你是否想過它給資料庫發送了多少次查詢?一次?還是兩次?或者 author、book、review 實體各一次?

實際上,這取決于資料庫中作者的人數。如果資料庫較小,裡面隻有11名作者和6本書。那麼這段代碼會觸發12次查詢,其中1次用于擷取所有作者姓名,另外11次給每位作者比對書名。這一問題被稱作 n+1 查詢問題,無論我們使用的是 mysql、sqlserver 還是其他資料庫,都容易出現此類問題。是以在生産環境中,随着資料量不斷增大,代碼的性能就越差。

我們可以通過多種方法,用一次查詢擷取所有要求的實體資訊 ,進而避免這一情況。在筆者看來,使用 <code>@namedentitygraph</code> 來解決此問題是最新,也最好的方法。

實體圖通過獨立于查詢的方法指定應該從資料庫中擷取的實體的圖。這意味着,你需要為實體圖建立一個獨立的定義,并在需要時與查詢合并。下段代碼展示了如何定義根據作者名提取書名的 <code>@namedentitygraph</code>。

現在,實體管理器可以用這個圖為參考,通過一次查詢擷取所有作者和書名。在圖的定義中可以看到,筆者隻提供了包含相關實體的屬性名稱。是以,筆者将<code>@namedentitygraph</code>作為loadgraph (負載圖),這樣便可提取其他所有屬性及其定義的擷取類型,如下所示:

該示例展示了一個非常簡單的實體圖,在實際的應用中,很可能會用到更複雜的圖,但這也不成問題。你可以定義多個 <code>@namedattributenodes</code> 以定義更複雜的圖,也可以用<code>@namedsubgraph</code> 注解來建立多層次的圖。如果想了解更多關于 <code>@namedentitygraphs</code>的資訊,請點選實體圖使用方式詳解。

在某些使用案例中,你可能還需要用更動态的方式來定義實體圖,比如,根據一些輸入參數進行定義。在此類案例中,通過 java api 用程式設計的方式定義實體圖效果更佳。

逐個更新實體是造成 jpa 性能問題的另一個常見原因。作為 java 開發者,我們習慣處理對象,并用面向對象的方式思考問題。盡管這是實作複雜邏輯和應用的好方法,但也是處理資料庫時導緻性能退化的一個常見原因。

從面向對象的角度來看,對實體進行更新和删除操作是完全可以接受的。但當你不得不更新一大組實體時,這種操作就會非常低效。持久性提供者(persistence provider)将為每個更新實體建立一個更新語句,并在下一次 flush 操作時發送至資料庫中。

然而,sql 提供了一個更為高效的方式。它允許你建立可一次性更新多個實體的更新語句。你還可以對 jpa 2.1 引入的 <code>criteriaupdate</code> 和 <code>criteriadelete</code> 語句進行同樣的操作。

如果你之前用過 <code>criteria</code> 條件查詢,肯定對新的 <code>criteriaupdate</code> 以及<code>criteriadelete</code> 語句非常熟悉,更新和删除操作的建立方式幾乎與 jpa 2.0 中引入的 criteria 條件查詢建立方式一樣。

在下面的代碼段中可以看到,你需要從實體管理器中擷取 <code>criteriabuilder</code> 并用它建立 <code>criteriaupdate</code> 對象,對 <code>criteriaquery</code> 進行的操作與此類似,主要差別在于用于定義更新操作的 <code>set</code> 方法。

在 <code>criteriadelete</code> 操作中,你隻需要在實體管理器中調用 <code>createcriteriadelete</code> 方法以擷取 <code>criteriadelete</code> 對象,并用它來定義與上例類似的 <code>from</code> 和 <code>where</code> 查詢部分。

作為 java 開發者,我們傾向于在 java 中實作所有的應用邏輯,這也是造成性能問題的一大常見原因。别誤會,在 java 中實作邏輯的好處很多,但如果将部分邏輯實作在資料庫中,隻把結果發送到業務邏輯層,也能得到很好的效果。

在資料庫中執行邏輯的方法很多。隻用 sql 語句,也能完成很多事情,如果不夠,你還可以調用資料庫的特定功能和存儲過程。在本文中,筆者将仔細探讨存儲過程,更确切地說是探讨調用存儲過程的方式。

在 jpa 2.0 中,并沒有針對存儲過程的實際支援,本地查詢是調用存儲過程的唯一方式。jpa 2.1.引入了 <code>@namedstoredprocedurequery</code> 和更為動态的<code>storedprocedurequery</code>,改變了這一現狀。在本文中,筆者将重點關注基于注解的、用 <code>@namedstoredprocedurequery</code> 進行調用的存儲過程的定義。筆者在自己的部落格中詳細介紹了動态存儲過程查詢 。

在下面代碼段中可以看到, <code>@namedstoredprocedurequery</code> 的定義非常簡潔,你需要指定查詢的名稱、資料庫中的存儲過程名稱以及輸入和輸出參數。在本例中,筆者用輸入參數 <code>x</code> 和 <code>y</code> 調用存儲過程 <code>calculate</code>,期望的輸出參數為 <code>sum</code>,其它支援的參數類型還有用于輸入和輸出的參數 <code>input</code> 和用于檢索結果集的 <code>ref_coursor</code>。

<code>@namedstoredprocedurequery</code> 的使用方法與 <code>@namedquery</code> 相似,你需要向實體管理器的<code>createnamedstoredprocedurequery</code> 方法提供查詢名稱,以便在本次查詢中擷取<code>storedprocedurequery</code> 對象,然後,用 <code>setparameter</code> 方法設定輸入參數,之後再用<code>execute</code> 方法調用存儲過程。

總結

jpa 給資料庫存儲和檢索帶來諸多便利。通過這一工具,可快速開展項目,解決大部分問題,但也更容易導緻實作非常低效的持久層。由此,普遍存在的問題包括:使用過多查詢擷取所需資料、逐個更新實體以及在 java 中執行所有邏輯。

jpa 2.1規範引入了幾個新的功能以應對這些低效操作,比如實體圖(entity graphs),條件更新(criteria update)和存儲過程查詢(stored procedure queries)。筆者的jpa2.1新功能備忘單囊括了jpa 2.1的這些功能及其他新功能,你可以免費下載下傳。

(編譯自:http://zeroturnaround.com/rebellabs/three-jpa-2-1-features-that-will-boost-your-applications-performance/)

oneapm 為您提供端到端的 java 應用性能解決方案,我們支援所有常見的 java 架構及應用伺服器,助您快速發現系統瓶頸,定位異常根本原因。分鐘級部署,即刻體驗,java 監控從來沒有如此簡單。想閱讀更多技術文章,請通路 oneapm 官方技術部落格。

本文轉自 oneapm 官方部落格