什麼時候使用Entity或DTO

關注公衆號: 鍋外的大佬java

每日推送國外優秀的技術翻譯文章,勵志幫助國內的開發者更好地成長!web

JPAHibernate容許你在JPQLCriteria查詢中使用DTOEntity做爲映射。當我在個人在線培訓或研討會上討論Hibernate性能時,我常常被問到,選擇使用適當的映射是不是重要的? 答案是:是的!爲你的用例選擇正確的映射會對性能產生巨大影響。我只選擇你須要的數據。很明顯,選擇沒必要要的信息不會爲你帶來任何性能優點。數據庫

1.DTO與Entity之間的主要區別

EntityDTO之間常被忽略的區別是——Entity被持久上下文(persistence context)所管理。當你想要更新Entity時,只須要調用setter方法設置新值。Hibernate將處理所需的SQL語句並將更改寫入數據庫。緩存

天下沒有免費的午飯。Hibernate必須對全部託管實體(managed entities)執行髒檢查(dirty checks),以肯定是否須要在數據庫中保存變動。這很耗時,當你只想向客戶端發送少許信息時,這徹底沒有必要。bash

你還須要記住,Hibernate和任何其餘JPA實現都將全部託管實體存儲在一級緩存中。這彷佛是一件好事。它能夠防止執行重複查詢,這是Hibernate寫入優化所必需的。可是,須要時間來管理一級緩存,若是查詢數百或數千個實體,甚至可能發生問題。app

使用Entity會產生開銷,而你能夠在使用DTO時避免這種開銷。但這是否意味着不該該使用Entity?顯然不是。函數

2.寫操做投影

實體投影(Entity Projections)適用於全部寫操做。Hibernate以及其餘JPA實現管理實體的狀態,並建立所需的SQL語句以在數據庫中保存更改。這使得大多數建立,更新和刪除操做的實現變得很是簡單和有效。性能

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

Author a = em.find(Author.class, 1L); a.setFirstName("Thorben"); em.getTransaction().commit(); em.close(); 

3.讀操做投影

可是隻讀(read-only)操做要用不一樣方式處理。若是想從數據庫中讀取數據,那麼Hibernate就不會管理狀態或執行髒檢查。 所以,從理論上說,對於讀取數據,DTO投影是更好的選擇。但真的有什麼不一樣嗎?我作了一個小的性能測試來回答這個問題。測試

3.1.測試設置

我使用如下領域模型進行測試。它由AuthorBook實體組成,使用多對一關聯(many-to-one)。因此,每本書都是由一位做者撰寫。fetch

@Entity public class Author {     @Id     @GeneratedValue(strategy = GenerationType.AUTO)     @Column(name = "id", updatable = false, nullable = false)     private Long id;     @Version     private int version;     private String firstName;     private String lastName;     @OneToMany(mappedBy = "author")     private List bookList = new ArrayList();     ... } 

要確保Hibernate不獲取任何額外的數據,我設置了@ManyToOneFetchTypeLAZH。你能夠閱讀 Introduction to JPA FetchTypes獲取不一樣FetchType及其效果的更多信息。

@Entity public class Book {     @Id     @GeneratedValue(strategy = GenerationType.AUTO)     @Column(name = "id", updatable = false, nullable = false)     private Long id;     @Version     private int version;     private String title;     @ManyToOne(fetch = FetchType.LAZY)     @JoinColumn(name = "fk_author")     private Author author;     ... } 

我用10個做者建立了一個測試數據庫,他們每人寫了10 本書,因此數據庫總共包含100 本書。在每一個測試中,我將使用不一樣的投影來查詢100 本書並測量執行查詢和事務所需的時間。爲了減小任何反作用的影響,我這樣作1000次並測量平均時間。 OK,讓咱們開始吧。

3.2.查詢實體

在大多數應用程序中,實體投影(Entity Projection)是最受歡迎的。有了EntityJPA能夠很容易地將它們用做投影。 運行這個小測試用例並測量檢索100個Book實體所需的時間。

long timeTx = 0; long timeQuery = 0; long iterations = 1000; // Perform 1000 iterations for (int i = 0; i < iterations; i++) {     EntityManager em = emf.createEntityManager();     long startTx = System.currentTimeMillis();     em.getTransaction().begin();     // Execute Query     long startQuery = System.currentTimeMillis();     List<Book> books = em.createQuery("SELECT b FROM Book b").getResultList();     long endQuery = System.currentTimeMillis();     timeQuery += endQuery - startQuery;     em.getTransaction().commit();     long endTx = System.currentTimeMillis();     em.close();     timeTx += endTx - startTx; } System.out.println("Transaction: total " + timeTx + " per iteration " + timeTx / (double)iterations); System.out.println("Query: total " + timeQuery + " per iteration " + timeQuery / (double)iterations); 

平均而言,執行查詢、檢索結果並將其映射到100個Book實體須要2ms。若是包含事務處理,則爲2.89ms。對於小型且不那麼新的筆記本電腦來講也不錯。

Transaction: total 2890 per iteration 2.89
Query: total 2000 per iteration 2.0

3.3.默認FetchType對To-One關聯的影響

當我向你展現Book實體時,我指出我將FetchType設置爲LAZY以免其餘查詢。默認狀況下,To-one關聯的FetchtTypeEAGER,它告訴Hibernate當即初始化關聯。

這須要額外的查詢,若是你的查詢選擇多個實體,則會產生巨大的性能影響。讓咱們更改Book實體以使用默認的FetchType並執行相同的測試。

@Entity public class Book {     @ManyToOne     @JoinColumn(name = "fk_author")     private Author author;     ... } 

這個小小的變化使測試用例的執行時間增長了兩倍多。如今花了7.797ms執行查詢並映射結果,而不是2毫秒。每筆交易的時間上升到8.681毫秒而不是2.89毫秒。

Transaction: total 8681 per iteration 8.681
Query: total 7797 per iteration 7.797

所以,最好確保To-one關聯設置FetchTypeLAZY

3.4.選擇@Immutable實體

Joao Charnet在評論中告訴我要在測試中添加一個不可變的實體(Immutable Entity)。有趣的問題是:返回使用@Immutable註解的實體,查詢性能會更好嗎?

Hibernate沒必要對這些實體執行任何髒檢查,由於它們是不可變的。這可能會帶來更好的表現。因此,讓咱們試一試。

我在測試中添加了如下ImmutableBook實體。

@Entity @Table(name = "book") @Immutable public class ImmutableBook {     @Id     @GeneratedValue(strategy = GenerationType.AUTO)     @Column(name = "id", updatable = false, nullable = false)     private Long id;     @Version     private int version;     private String title;     @ManyToOne(fetch = FetchType.LAZY)     @JoinColumn(name = "fk_author")     private Author author;     ... } 

它是Book實體的副本,帶有2個附加註解。@Immutable註解告訴Hibernate,這個實體是不可變得。而且@Table(name =「book」)將實體映射到book表。所以,咱們可使用與之前相同的數據運行相同的測試。

long timeTx = 0; long timeQuery = 0; long iterations = 1000; // Perform 1000 iterations for (int i = 0; i < iterations; i++) {     EntityManager em = emf.createEntityManager();     long startTx = System.currentTimeMillis();     em.getTransaction().begin();     // Execute Query     long startQuery = System.currentTimeMillis();     List<Book> books = em.createQuery("SELECT b FROM ImmutableBook b")             .getResultList();     long endQuery = System.currentTimeMillis();     timeQuery += endQuery - startQuery;     em.getTransaction().commit();     long endTx = System.currentTimeMillis();     em.close();     timeTx += endTx - startTx; } System.out.println("Transaction: total " + timeTx + " per iteration " + timeTx / (double)iterations); System.out.println("Query: total " + timeQuery + " per iteration " + timeQuery / (double)iterations); 

有趣的是,實體是不是不可變的,對查詢沒有任何區別。測量的事務和查詢的平均執行時間幾乎與先前的測試相同。

Transaction: total 2879 per iteration 2.879
Query: total 2047 per iteration 2.047

3.5.使用QueryHints.HINT_READONLY查詢Entity

Andrew Bourgeois建議在測試中包含只讀查詢。因此,請看這裏。

此測試使用我在文章開頭向你展現的Book實體。但它須要測試用例進行修改。

JPAHibernate支持一組查詢提示(hits),容許你提供有關查詢及其執行方式的其餘信息。查詢提示QueryHints.HINT_READONLY告訴Hibernate以只讀模式查詢實體。所以,Hibernate不須要對它們執行任何髒檢查,也能夠應用其餘優化。

你能夠經過在Query接口上調用setHint方法來設置此提示。

long timeTx = 0; long timeQuery = 0; long iterations = 1000; // Perform 1000 iterations for (int i = 0; i < iterations; i++) {     EntityManager em = emf.createEntityManager();     long startTx = System.currentTimeMillis();     em.getTransaction().begin();     // Execute Query     long startQuery = System.currentTimeMillis();     Query query = em.createQuery("SELECT b FROM Book b");     query.setHint(QueryHints.HINT_READONLY, true);     query.getResultList();     long endQuery = System.currentTimeMillis();     timeQuery += endQuery - startQuery;     em.getTransaction().commit();     long endTx = System.currentTimeMillis();     em.close();     timeTx += endTx - startTx; } System.out.println("Transaction: total " + timeTx + " per iteration " + timeTx / (double)iterations); System.out.println("Query: total " + timeQuery + " per iteration " + timeQuery / (double)iterations); 

你可能但願將查詢設置爲只讀來讓性能顯著的提高——Hibernate執行了更少的工做,所以應該更快。

但正如你在下面看到的,執行時間幾乎與以前的測試相同。至少在此測試場景中,將QueryHints.HINT_READONLY設置爲true不會提升性能。

Transaction: total 2842 per iteration 2.842
Query: total 2006 per iteration 2.006

3.6.查詢DTO

加載100 本書實體大約須要2ms。讓咱們看看在JPQL查詢中使用構造函數表達式獲取相同的數據是否表現更好。

固然,你也能夠在Criteria查詢中使用構造函數表達式。

long timeTx = 0; long timeQuery = 0; long iterations = 1000; // Perform 1000 iterations for (int i = 0; i < iterations; i++) {     EntityManager em = emf.createEntityManager();     long startTx = System.currentTimeMillis();     em.getTransaction().begin();     // Execute the query     long startQuery = System.currentTimeMillis();     List<BookValue> books = em.createQuery("SELECT new org.thoughts.on.java.model.BookValue(b.id, b.title) FROM Book b").getResultList();     long endQuery = System.currentTimeMillis();     timeQuery += endQuery - startQuery;     em.getTransaction().commit();     long endTx = System.currentTimeMillis();     em.close();     timeTx += endTx - startTx; } System.out.println("Transaction: total " + timeTx + " per iteration " + timeTx / (double)iterations); System.out.println("Query: total " + timeQuery + " per iteration " + timeQuery / (double)iterations); 

正如所料,DTO投影比實體(Entity)投影表現更好。

Transaction: total 1678 per iteration 1.678
Query: total 1143 per iteration 1.143

平均而言,執行查詢須要1.143ms,執行事務須要1.678ms。查詢的性能提高43%,事務的性能提升約42%。

對於一個花費一分鐘實現的小改動而言,這已經很不錯了。

在大多數項目中,DTO投影的性能提高將更高。它容許你選擇用例所需的數據,而不只僅是實體映射的全部屬性。選擇較少的數據幾乎總能帶來更好的性能。

4.摘要

爲你的用例選擇正確的投影比你想象的更容易也更重要。

若是要實現寫入操做,則應使用實體(Entity)做爲投影。Hibernate將管理其狀態,你只需在業務邏輯中更新其屬性。而後Hibernate會處理剩下的事情。

你已經看到了個人小型性能測試的結果。個人筆記本電腦可能不是運行這些測試的最佳環境,它確定比生產環境慢。可是性能的提高是如此之大,很明顯你應該使用哪一種投影。

file

 

使用DTO投影的查詢比選擇實體的查詢快約40%。所以,最好花費額外的精力爲你的只讀操做建立DTO並將其用做投影。

此外,還應確保對全部關聯使用FetchType.LAZY。正如在測試中看到的那樣,即便是一個熱切獲取to-one的關聯操做,也可能會將查詢的執行時間增長兩倍。所以,最好使用FetchType.LAZY並初始化你的用例所需的關係

原文連接:thoughts-on-java.org/entities-dt…

做者: Thorben Janssen

譯者:Yunooa

 

相關文章
相關標籤/搜索