1. 程式人生 > >Java程式設計師注意——扼殺效能的 10 個常見 Hibernate 錯誤

Java程式設計師注意——扼殺效能的 10 個常見 Hibernate 錯誤

那麼請閱讀這篇文章!

我在很多應用程式中修復過效能問題,其中大部分都是由同樣的錯誤引起的。修復之後,效能變得更溜,而且其中的大部分問題都很簡單。所以,如果你想改進應用程式,那麼可能也是小菜一碟。

這裡列出了導致Hibernate效能問題的10個最常見的錯誤,以及如何修復它們。

錯誤1:使用Eager Fetching

FetchType.EAGER的啟示已經討論了好幾年了,而且有很多文章對它進行了詳細的解釋。我自己也寫了一篇。但不幸的是,它仍然是效能問題最常見的兩個原因之一。

FetchType定義了Hibernate何時初始化關聯。你可以使用@OneToMany,@ManyToOne,@ManyToMany和@OneToOneannotation註釋的fetch屬性進行指定。

@Entity
public class Author{
 
    @ManyToMany(mappedBy="authors", fetch=FetchType.LAZY)
    private List<Book> books = new ArrayList<Book>();
 
    ...
 
}

當Hibernate載入一個實體的時候,它也會即時載入獲取的關聯。例如,當Hibernate載入Author實體時,它也提取相關的Book實體。這需要對每個Author進行額外的查詢,因此經常需要幾十甚至數百個額外的查詢。

這種方法是非常低效的,因為Hibernate不管你是不是要使用關聯都會這樣做。最好改用FetchType.LAZY代替。它會延遲關係的初始化,直到在業務程式碼中使用它。這可以避免大量不必要的查詢,並提高應用程式的效能。

幸運的是,JPA規範將FetchType.LAZY定義為所有對多關聯的預設值。所以,你只需要確保你不改變這個預設值即可。但不幸的是,一對一關係並非如此。

錯誤2:忽略一對一關聯的預設FetchType

接下來,為了防止立即抓取(eager fetching),你需要做的是對所有的一對一關聯更改預設的FetchType。不幸的是,這些關係在預設情況下會被即時抓取。在一些用例中,那並非一個大問題,因為你只是載入了一個額外的資料庫記錄。但是,如果你載入多個實體,並且每個實體都指定了幾個這樣的關聯,那麼很快就會積少成多,水滴石穿。

所以,最好確保所有的一對一關聯設定FetchType為LAZY。

@Entity
public class Review {
 
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "fk_book")
    private Book book;
 
    ...
 
}

錯誤3:不要初始化所需的關聯

當你對所有關聯使用FetchType.LAZY以避免錯誤1和錯誤2時,你會在程式碼中發現若干n+1選擇問題。當Hibernate執行1個查詢來選擇n個實體,然後必須為每個實體執行一個額外的查詢來初始化一個延遲的獲取關聯時,就會發生這個問題。

Hibernate透明地獲取惰性關係,因此在程式碼中很難找到這種問題。你只要呼叫關聯的getter方法,我想我們大家都不希望Hibernate執行任何額外的查詢吧。

List<Author> authors = em.createQuery("SELECT a FROM Author a", Author.class).getResultList();
for (Author a : authors) {
    log.info(a.getFirstName() + " " + a.getLastName() + " wrote "
            + a.getBooks().size() + " books.");
}

如果你使用開發配置啟用Hibernate的統計元件並監視已執行的SQL語句的數量,n+1選擇問題就會更容易被發現。

15:06:48,362 INFO [org.hibernate.engine.internal.StatisticalLoggingSessionEventListener] - Session Metrics {
  28925 nanoseconds spent acquiring 1 JDBC connections;
  24726 nanoseconds spent releasing 1 JDBC connections;
  1115946 nanoseconds spent preparing 13 JDBC statements;
  8974211 nanoseconds spent executing 13 JDBC statements;
  0 nanoseconds spent executing 0 JDBC batches;
  0 nanoseconds spent performing 0 L2C puts;
  0 nanoseconds spent performing 0 L2C hits;
  0 nanoseconds spent performing 0 L2C misses;
  20715894 nanoseconds spent executing 1 flushes (flushing a total of 13 entities and 13 collections);
  88175 nanoseconds spent executing 1 partial-flushes (flushing a total of 0 entities and 0 collections)
}

正如你所看到的JPQL查詢和對12個選定的Author實體的每一個呼叫getBooks方法,導致了13個查詢。這比大多數開發人員所以為的還要多,在他們看到如此簡單的程式碼片段的時候。

如果你讓Hibernate初始化所需的關聯,那麼你可以很容易地避免這種情況。有若干不同的方式可以做到這一點。最簡單的方法是新增JOIN FETCH語句到FROM子句中。

Author a = em.createQuery(
                "SELECT a FROM Author a JOIN FETCH a.books WHERE a.id = 1",
                Author.class).getSingleResult();

錯誤4:選擇比所需的更多記錄

當我告訴你選擇太多的記錄會減慢應用程式的速度時,我敢保證你一定不會感到驚訝。但是我仍然經常會發現這個問題,當我在諮詢電話中分析應用程式的時候。

其中一個原因可能是JPQL不支援你在SQL查詢中使用OFFSET和LIMIT關鍵字。這看起來似乎不能限制查詢中檢索到的記錄數量。但是,你可以做到這一點。你只需要在Query介面上,而不是在JPQL語句中設定此資訊。

我在下面的程式碼片段中做到這一點。我首先通過id排序選定的Author實體,然後告訴Hibernate檢索前5個實體。

List<Author> authors = em.createQuery("SELECT a FROM Author a ORDER BY a.id ASC", Author.class)
                                    .setMaxResults(5)
                                    .setFirstResult(0)
                                    .getResultList();

錯誤5:不使用繫結引數

繫結引數是查詢中的簡單佔位符,並提供了許多與效能無關的好處:

  • 它們非常易於使用。
  • Hibernate自動執行所需的轉換。
  • Hibernate會自動轉義Strings,防止SQL注入漏洞。

而且也可以幫助你實現一個高效能的應用程式。

大多數應用程式執行大量相同的查詢,只在WHERE子句中使用了一組不同的引數值。繫結引數允許Hibernate和資料庫識別與優化這些查詢。

你可以在JPQL語句中使用命名的繫結引數。每個命名引數都以“:”開頭,後面跟它的名字。在查詢中定義了繫結引數後,你需要呼叫Query介面上的setParameter方法來設定繫結引數值。

TypedQuery<Author> q = em.createQuery(
                "SELECT a FROM Author a WHERE a.id = :id", Author.class);
q.setParameter("id", 1L);
Author a = q.getSingleResult();

錯誤6:執行業務程式碼中的所有邏輯

對於Java開發人員來說,在業務層實現所有的邏輯是自然而然的。我們可以使用我們最熟悉的語言、庫和工具。

但有時候,在資料庫中實現操作大量資料的邏輯會更好。你可以通過在JPQL或SQL查詢中呼叫函式或者使用儲存過程來完成。

讓我們快速看看如何在JPQL查詢中呼叫函式。如果你想深入探討這個話題,你可以閱讀我關於儲存過程的文章。

你可以在JPQL查詢中使用標準函式,就像在SQL查詢中呼叫它們一樣。你只需引用該函式的名稱,後跟一個左括號,一個可選的引數列表和一個右括號。

Query q = em.createQuery("SELECT a, size(a.books) FROM Author a GROUP BY a.id");
List<Object[]> results = q.getResultList();

並且,通過JPA的函式function,你也可以呼叫資料庫特定的或自定義的資料庫函式。

TypedQuery<Book> q = em.createQuery(
             "SELECT b FROM Book b WHERE b.id = function('calculate', 1, 2)",
             Book.class);
Book b = q.getSingleResult();

錯誤7:無理由地呼叫flush方法

這是另一個比較普遍的錯誤。開發人員在持久化一個新實體或更新現有實體後,呼叫EntityManager的flush方法時經常會出現這個錯誤。這迫使Hibernate對所有被管理的實體執行髒檢查,併為所有未決的插入、更新或刪除操作建立和執行SQL語句。這會減慢應用程式,因為它阻止了Hibernate使用一些內部優化。

Hibernate將所有被管理的實體儲存在永續性上下文中,並試圖儘可能延遲寫操作的執行。這允許Hibernate將同一實體上的多個更新操作合併為一個SQL UPDATE語句,通過JDBC批處理繫結多個相同的SQL語句,並避免執行重複的SQL語句,這些SQL語句返回你已在當前Session中使用的實體。

作為一個經驗法則,你應該避免任何對flush方法的呼叫。JPQL批量操作是罕見的例外之一,對此我將在錯誤9中解釋。

錯誤8:使用Hibernate應付一切

Hibernate的物件關係對映和各種效能優化使大多數CRUD用例的實現非常簡單和高效。這使得Hibernate成為許多專案的一個很好的選擇。但這並不意味著Hibernate對於所有的專案都是一個很好的解決方案。

我在我之前的一個帖子和視訊中詳細討論過這個問題。JPA和Hibernate為大多數建立、讀取或更新一些資料庫記錄的標準CRUD用例提供了很好的支援。對於這些用例,物件關係對映可以大大提升生產力,Hibernate的內部優化提供了一個很優越的效能。

但是,當你需要執行非常複雜的查詢、實施分析或報告用例或對大量記錄執行寫操作時,結果就不同了。所有這些情況都不適合JPA和Hibernate的查詢能力以及基於實體管理的生命週期。

如果這些用例只佔應用程式的一小部分,那麼你仍然可以使用Hibernate。但總的來說,你應該看看其他的框架,比如jOOQ或者Querydsl,它們更接近於SQL,並且可以避免任何物件關係對映。

錯誤9:逐個更新或刪除巨大的實體列表

在你看著你的Java程式碼時,感覺逐個地更新或刪除實體也可以接受。這就是我們對待物件的方式,對吧?

這可能是處理Java物件的標準方法,但如果你需要更新大量的資料庫記錄,那麼,這就不是一個好方法了。在SQL中,你只需一次定義一個影響多個記錄的UPDATE或DELETE語句。資料庫將會非常高效地處理這些操作。

不幸的是,用JPA和Hibernate操作起來則沒有那麼容易。每個實體都有自己的生命週期,而你如果要更新或刪除多個實體的話,則首先需要從資料庫載入它們。然後在每個實體上執行操作,Hibernate將為每個實體生成所需的SQL UPDATE或DELETE語句。因此,Hibernate不會只用1條語句來更新1000條資料庫記錄,而是至少會執行1001條語句。

很顯然,執行1001條語句比僅僅執行1條語句需要花費更多的時間。幸運的是,你可以使用JPQL、原生SQL或Criteria查詢對JPA和Hibernate執行相同的操作。

但是它有一些你應該知道的副作用。在資料庫中執行更新或刪除操作時,將不使用實體。這提供了更佳的效能,但它同時忽略了實體生命週期,並且Hibernate不能更新任何快取。

簡而言之,在執行批量更新之前,你不應使用任何生命週期偵聽器以及在EntityManager上呼叫flush和clear方法。flush方法將強制Hibernate在clear方法從當前持久化上下文中分離所有實體之前,將所有待處理的更改寫入資料庫。

em.flush();
em.clear();
Query query = em.createQuery("UPDATE Book b SET b.price = b.price*1.1");
query.executeUpdate();

錯誤10:使用實體進行只讀操作

JPA和Hibernate支援一些不同的projections。如果你想優化你的應用程式的效能,那麼你應該使用projections。最明顯的原因是你應該只選擇用例中需要的資料。

但這不是唯一的原因。正如我在最近的測試中顯示的那樣,即使你讀取了相同的資料庫列,DTO projections也比實體快得多。

在SELECT子句中使用建構函式表示式而不是實體只是一個小小的改變。但在我的測試中,DTO projections比實體快40%。當然,兩者比較的數值取決於你的用例,而且你也不應該通過這樣一個簡單而有效的方式來提高效能。

瞭解如何查詢和修復Hibernate效能問題

正如你所看到的,一些小小的問題都可能會減慢你的應用程式。但幸運的是,我們可以輕鬆避免這些問題並構建高效能持久層。

寫在最後

  • 第一:看完點贊,感謝您的認可;
  • ...
  • 第二:隨手轉發,分享知識,讓更多人學習到;
  • ...
  • 第三:記得點關注,每天更新的!!!
  • ...

相關推薦

Java程式設計師注意——扼殺效能10 常見 Hibernate 錯誤

那麼請閱讀這篇文章! 我在很多應用程式中修復過效能問題,其中大部分都是由同樣的錯誤引起的。修復之後,效能變得更溜,而且其中的大部分問題都很簡單。所以,如果你想改進應用程式,那麼可能也是小菜一碟。 這裡列出了導致Hibernate效能問題的10個最常見的錯誤,以及如何修復它們。 錯誤1:使用Eager Fet

高階 Java 程式設計師必須突破的 10 知識點!

工作多少年了,還在傳統公司寫if / for 等簡單的程式碼?那你就真的要被社會淘汰了,工作多年其實你與初級工程師又有多少區別呢?那麼作為一個高階Java攻城獅需要突破哪些知識點呢? 1、Java基礎技術體系、JVM記憶體分配、垃圾回收、類裝載機制、效能優化、反射機制、多執行緒、網路程式設計、常

java程式設計師應當知道的10面向物件設計原則

面向物件設計原則是OOPS程式設計的核心, 但我見過的大多數Java程式設計師熱心於像Singleton (單例) 、 Decorator(裝飾器)、Observer(觀察者) 等設計模式,而沒有把足夠多的注意力放在學習面向物件的分析和設計上面。學習面向物件程式設計像“抽象”

Java程式設計師變優秀的10要點

1.擁有紮實的基礎和深刻理解OO原則 對於Java程式設計師,深刻理解面向物件程式設計這一概念是必須的。沒有OOPS的堅實基礎,就領會不了像Java這些面向物件程式語言的美。光學習OO原則的定義用處不大,關鍵是要學會如何應用這些原則用一種OO的方式去設計解決方案。因此,我們應該對物件建模

Java程式設計師容易犯的10錯誤

Array 轉 ArrayList 一般開發者喜歡用: List list = Arrays.asList(arr); Arrays.asList() 會返回一個ArrayList,這是Arrays裡內嵌的一個私有靜態類,而並不是java.util.Arra

JAVA 程式設計師需要用到 10 測試框架和庫

想要提高你的自動化測試技術?以下是 10 個優秀的測試框架和庫,以及它們常見用法的概述。 最近我寫了一些文章,關於 Java 程式設計師今年應該學習什麼,例如程式語言,庫和框架等,如果只能學習或提高其中一項,那必然是自動化測試技能。 測試是專業程式設計師區別於業餘程式設計師的一項指標,作為專業程式設計師,並

Java程式設計師必須知道的10除錯技巧

除錯可以幫助識別和解決應用程式缺陷,在本文中,將使用大家常用的的開發工具Eclipse來除錯Java應用程式。 但這裡介紹的除錯方法基本都是通用的,也適用於NetBeans IDE,我們會把重點放在執行時上面。 在本文中使用的是Eclipse Juno版(Eclipse 4.2),在開始前給

Java程式設計師應該瞭解的10面向物件設計原則

摘要:Java程式設計最基本的原則就是要追求高內聚和低耦合的解決方案和程式碼模組設計。檢視Apache和Sun的開放原始碼能幫助你發現其他Java設計原則在這些程式碼中的實際運用。 面向物件設計原則是OOPS(Object-Oriented Programming Sys

Java 程式設計師應該知道的10面向物件理論

面向物件理論是面向物件程式設計的核心,但是我發現大部分Java程式設計師熱衷於像單例模式、裝飾者模式或觀察者模式這樣的設計模式,而並沒有十分注意學習面向物件的分析和設計。學習面向程式設計的基礎(如抽象,封裝,多型,繼承等)是非常重要的,而運用它們來設計乾淨的模組

Java程式設計師應瞭解的10面向物件設計原則

面向物件設計原則是 OOPS(Object-Oriented Programming System,面向物件的程式設計系統)程式設計的核心,但大多數 Java 程式設計師追逐像 Singleton、Decorator、Observer 這樣的設計模式,而不重

Java程式設計師應該瞭解的10面向物件…

     面向物件設計原則是OOPS(Object-Oriented Programming System,面向物件的程式設計系統)程式設計的核心,但大多數Java程式設計師追逐像Singleton、Decorator、Observer這樣的設計模式,而不重視面向物件的分析和設計。甚至還有經驗豐富的

Java程式設計師應該瞭解的10面向物件的設計原則

面向物件設計原則是OOPS(Object-Oriented Programming System,面向物件的程式設計系統)程式設計的核心,但大多數Java程式設計師追逐像Singleton、Decorator、Observer這樣的設計模式,而不重視面向物件的分析和設計。

扼殺效能10 常見 Hibernate 錯誤

錯誤1:使用Eager Fetching FetchType.EAGER的啟示已經討論了好幾年了,而且有很多文章對它進行了詳細的解釋。我自己也寫了一篇。但不幸的是,它仍然是效能問題最常見的兩個原因之一。 FetchType定義了Hibernate何時初始化關聯。你可以

零基礎學習Java程式設計師最高效的六建議

零基礎學習Java程式設計師最高效的六個建議 零基礎學習Java程式設計師最高效的六個建議 零基礎學習Java程式設計師最高效的六個建議 知識改變命運,對於Java程式設計師來說,技術不斷更新,只有及時充電,才能不被市場淘汰。今天為大家分享Java程式設計師學習的6個小技巧。 1、一

Java程式設計師不可不知的幾網站,你去過幾

看到網上IT友人積累的網址,非常不錯,不少我也知道,非常值得去學習,特此也借他人幫助記錄一下!!非常感謝!! 轉自:http://ibeginner.sinaapp.com/index.php?m=Home&c=Index&a=detail&i

程式設計師減壓放鬆的 10 良心網站

工作之餘,不妨放下微博跟朋友圈,來這10個網站感受一下看著就醉了的情境:「唸完往上一推音樂鍵,我往後一靠,潮乎乎的軟皮耳機裡頭,音樂排山倒海。」今天推薦的網站,利用代入感強的圖片與音訊,迅速幫你抹平焦慮,獲得平和心態,特別獻需求改千遍的程式設計師們。 1.Calm 這是同類型中最火的網站了,

決定Java程式設計師工資高低的三因素

因為工資高,吸引了一大批人紛紛加入IT行業。的確,就目前的形勢來看,IT行業的平均工資確實高於一般行業,但這並以為只要進入這一行就是高工資,想要獲得高工資還是看個人技術和其他因素的。 本篇文章總結了影響Java程式設計師工資高低的三個因素,大家可以酌情參考:   1、基本

java程式猿應該瞭解的10面向物件設計原則(每次看都很有感悟,特意拿來和大家共享)

Java程式設計最基本的原則就是要追求高內聚和低耦合的解決方案和程式碼模組設計。檢視Apache和Sun的開放原始碼能幫助你發現其他Java設計原則在這些程式碼中的實際運用。 面向物件設計原則是OOPS(Object-Oriented Programming System,

休息不夠、程式碼混亂、傲慢……程式設計師需要避免的 10 壞習慣

1. 休息不夠 我敢肯定你們很多人或者說幾乎所有人都對這個壞習慣感到十分罪惡。 我也是,對沒有休息或休息不夠依然有罪惡感。曾有一段時間,我凌晨六點入睡,中午一點左右起來吃午飯,一直工作到第二天凌晨六七點。這簡直太常見了,幾乎每天都是如此。在工作任務緊急的時候,我做過許多可笑的事情。我想

Java程式設計師注意:Tomcat Get請求的巨坑!

Tomcat8.5,當Get請求中包含了未經編碼的中文字元時,會報以下錯誤,請求未到應用程式在Tomcat層就被攔截了。 Tomcat報錯: java.lang.IllegalArgumentException: Invalid character