摘要:只用語句,也能完成很多事情,如果不夠,你還可以調用數據庫的特定功能和存儲過程。在中,并沒有針對存儲過程的實際支持,本地查詢是調用存儲過程的唯一方式。規范引入了幾個新的功能以應對這些低效操作,比如實體圖,條件更新和存儲過程查詢。
經常在網上看到開發者們抱怨 JPA 性能低下的帖子或文章,但如果仔細查看這些性能問題,常會發現導致問題的根本原因大致包括以下幾個:
使用過多的 SQL 查詢從數據庫中獲取所需的實體信息,即我們常說的n+1查詢問題
逐個更新實體,而不是使用單條語句進行更新
使用 Java 應用程序而非數據庫進行大量數據處理
JPA提供了處理這類問題的方法,并給 JPA2.1 增加了一些額外功能,可以極大地提升性能表現,筆者將在本文中解釋如何利用 JPA2.1 的功能避免上述問題。
順便提一下,如果想了解Java項目中更多的典型性能問題,可以參考筆者最近發布的基于性能調查結果的深度報告,如果你在尋找 JPA 資源,點擊此鏈接便可獲取JPA2.1特征的備忘清單。接下來我們來看看如何用JPA來解決現有的性能問題。
解決「SQL 查詢過多」的問題根據以往的經驗,使用過多的 SQL 查詢獲取所要求的實體是導致性能問題最普遍的原因。
即使是看起來最簡單的查詢,如果操作不當,也會觸發幾十次甚至上百次的 SQL 查詢。而且,你在本節中可以看到,這類不當操作不一定會出現在查詢語句中,而可能只是幾個配置不當的注解。所以,如果你覺得這個問題不會造成影響,請三思。
如果在你的項目中出現以下幾段代碼,你會怎么想?
List authors = this.em.createQuery("SELECT a FROM Author a", Author.class).getResultList(); for (Author a : authors) { System.out.println("作者 " + a.getFirstName() + " " + a.getLastName() + " 書籍信息 " + a.getBooks() .stream() .map(b -> b.getTitle() + "(" + b.getReviews().size() + " 評論)") .collect(Collectors.joining(", "))); }
上面的代碼段會打印所有作者的姓名及其書名,看起來非常簡單,但你是否想過它給數據庫發送了多少次查詢?一次?還是兩次?或者 Author、Book、Review 實體各一次?
實際上,這取決于數據庫中作者的人數。如果數據庫較小,里面只有11名作者和6本書。那么這段代碼會觸發12次查詢,其中1次用于獲取所有作者姓名,另外11次給每位作者匹配書名。這一問題被稱作 n+1 查詢問題,無論我們使用的是 MySQL、SqlServer 還是其他數據庫,都容易出現此類問題。因此在生產環境中,隨著數據量不斷增大,代碼的性能就越差。
我們可以通過多種方法,用一次查詢獲取所有要求的實體信息 ,從而避免這一情況。在筆者看來,使用 @NamedEntityGraph 來解決此問題是最新,也最好的方法。
實體圖通過獨立于查詢的方法指定應該從數據庫中獲取的實體的圖。這意味著,你需要為實體圖創建一個獨立的定義,并在需要時與查詢合并。下段代碼展示了如何定義根據作者名提取書名的 @NamedEntityGraph。
@Entity @NamedEntityGraph(name = "graph.AuthorBooks", attributeNodes = @NamedAttributeNode("books")) public class Author implements Serializable { … }
現在,實體管理器可以用這個圖為參考,通過一次查詢獲取所有作者和書名。在圖的定義中可以看到,筆者只提供了包含相關實體的屬性名稱。因此,筆者將@NamedEntityGraph作為loadgraph (負載圖),這樣便可提取其他所有屬性及其定義的獲取類型,如下所示:
EntityGraph graph = this.em.getEntityGraph("graph.AuthorBooks"); List authors = this.em .createQuery("SELECT DISTINCT a FROM Author a", Author.class) .setHint("javax.persistence.loadgraph", graph).getResultList();
該示例展示了一個非常簡單的實體圖,在實際的應用中,很可能會用到更復雜的圖,但這也不成問題。你可以定義多個 @NamedAttributeNodes 以定義更復雜的圖,也可以用 @NamedSubGraph 注解來創建多層次的圖。如果想了解更多關于 @NamedEntityGraphs 的信息,請點擊實體圖使用方式詳解。
在某些使用案例中,你可能還需要用更動態的方式來定義實體圖,比如,根據一些輸入參數進行定義。在此類案例中,通過 Java API 用編程的方式定義實體圖效果更佳。
解決「逐個更新實體」的問題逐個更新實體是造成 JPA 性能問題的另一個常見原因。作為 Java 開發者,我們習慣處理對象,并用面向對象的方式思考問題。盡管這是實現復雜邏輯和應用的好方法,但也是處理數據庫時導致性能退化的一個常見原因。
從面向對象的角度來看,對實體進行更新和刪除操作是完全可以接受的。但當你不得不更新一大組實體時,這種操作就會非常低效。持久性提供者(Persistence Provider)將為每個更新實體創建一個更新語句,并在下一次 flush 操作時發送至數據庫中。
然而,SQL 提供了一個更為高效的方式。它允許你創建可一次性更新多個實體的更新語句。你還可以對 JPA 2.1 引入的 CriteriaUpdate 和 CriteriaDelete 語句進行同樣的操作。
如果你之前用過 criteria 條件查詢,肯定對新的 CriteriaUpdate 以及 CriteriaDelete 語句非常熟悉,更新和刪除操作的創建方式幾乎與 JPA 2.0 中引入的 criteria 條件查詢創建方式一樣。
在下面的代碼段中可以看到,你需要從實體管理器中獲取 CriteriaBuilder 并用它創建 CriteriaUpdate 對象,對 CriteriaQuery 進行的操作與此類似,主要區別在于用于定義更新操作的 set 方法。
CriteriaBuilder cb = this.em.getCriteriaBuilder(); // create update CriteriaUpdate update = cb.createCriteriaUpdate(Author.class); // set the root class Root a = update.from(Author.class); // set update and where clause update.set(Author_.firstName, cb.concat(a.get(Author_.firstName), " - updated")); update.where(cb.greaterThanOrEqualTo(a.get(Author_.id), 3L)); // perform update Query q = this.em.createQuery(update); q.executeUpdate();
在 CriteriaDelete 操作中,你只需要在實體管理器中調用 createCriteriaDelete 方法以獲取 CriteriaDelete 對象,并用它來定義與上例類似的 FROM 和 WHERE 查詢部分。
在數據庫中處理數據作為 Java 開發者,我們傾向于在 Java 中實現所有的應用邏輯,這也是造成性能問題的一大常見原因。別誤會,在 Java 中實現邏輯的好處很多,但如果將部分邏輯實現在數據庫中,只把結果發送到業務邏輯層,也能得到很好的效果。
在數據庫中執行邏輯的方法很多。只用 SQL 語句,也能完成很多事情,如果不夠,你還可以調用數據庫的特定功能和存儲過程。在本文中,筆者將仔細探討存儲過程,更確切地說是探討調用存儲過程的方式。
在 JPA 2.0 中,并沒有針對存儲過程的實際支持,本地查詢是調用存儲過程的唯一方式。JPA 2.1.引入了 @NamedStoredProcedureQuery 和更為動態的 StoredProcedureQuery,改變了這一現狀。在本文中,筆者將重點關注基于注解的、用 @NamedStoredProcedureQuery 進行調用的存儲過程的定義。筆者在自己的博客中詳細介紹了動態存儲過程查詢 。
在下面代碼段中可以看到, @NamedStoredProcedureQuery 的定義非常簡潔,你需要指定查詢的名稱、數據庫中的存儲過程名稱以及輸入和輸出參數。在本例中,筆者用輸入參數 x 和 y 調用存儲過程 calculate,期望的輸出參數為 sum,其它支持的參數類型還有用于輸入和輸出的參數 INPUT 和用于檢索結果集的 REF_COURSOR。
@NamedStoredProcedureQuery( name = "calculate", procedureName = "calculate", parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, type = Double.class, name = "x"), @StoredProcedureParameter(mode = ParameterMode.IN, type = Double.class, name = "y"), @StoredProcedureParameter(mode = ParameterMode.OUT, type = Double.class, name = "sum") })
@NamedStoredProcedureQuery 的使用方法與 @NamedQuery 相似,你需要向實體管理器的createNamedStoredProcedureQuery 方法提供查詢名稱,以便在本次查詢中獲取 StoredProcedureQuery 對象,然后,用 setParameter 方法設定輸入參數,之后再用 execute 方法調用存儲過程。
StoredProcedureQuery query = this.em.createNamedStoredProcedureQuery("calculate"); query.setParameter("x", 1.23d); query.setParameter("y", 4.56d); query.execute(); Double sum = (Double) query.getOutputParameterValue("sum");總結
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 官方博客
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65344.html
摘要:只用語句,也能完成很多事情,如果不夠,你還可以調用數據庫的特定功能和存儲過程。在中,并沒有針對存儲過程的實際支持,本地查詢是調用存儲過程的唯一方式。規范引入了幾個新的功能以應對這些低效操作,比如實體圖,條件更新和存儲過程查詢。 經常在網上看到開發者們抱怨 JPA 性能低下的帖子或文章,但如果仔細查看這些性能問題,常會發現導致問題的根本原因大致包括以下幾個: 使用過多的 SQL 查詢從...
閱讀 1120·2021-11-25 09:43
閱讀 1569·2021-10-25 09:47
閱讀 2465·2019-08-30 13:46
閱讀 753·2019-08-29 13:45
閱讀 1280·2019-08-26 13:29
閱讀 2989·2019-08-23 15:30
閱讀 1102·2019-08-23 14:17
閱讀 1325·2019-08-23 13:43