使用 Hibernate 和 MySQL 須要知道的五件事

https://www.thoughts-on-java.org/5-things-you-need-to-know-when-using-hibernate-with-mysql/
做者:Thorben Janssen
譯者:oopsguy.comhtml

使用 JPA 和 Hibernate 的其一好處是它提供了數據庫特定方言和功能抽象。所以,理論上,你能夠實現一個應用程序,將其鏈接到一個受支持的數據庫,而且它能夠在不用更改任何代碼的狀況下運行。java

Hibernate 真的很好。但老實說,你有沒有想過本身的應用程序真的能與每一個支持的數據庫完美運行嗎?mysql

Hibernate 幫你作了不少重要的事。可是,若是你但願應用程序可以穩定運行,你仍須要知道要使用哪一種數據庫,並相應地調整配置和代碼。sql

在以前的一篇文章中,我討論了「使用 Hibernate 和 PostgreSQL 數據庫須要知道的 6 件事情」。今天我想仔細討論 MySQL 數據庫。數據庫

一、映射:主鍵

處理和建立主鍵是很基礎的內容,但也是應用程序最重要的一部分。編程

JPA 規範的 @GeneratedValue 註解容許你定義用於建立惟一主鍵值的策略。你能夠選擇 SEQUENCE、IDENTITY、TABLE 和 AUTOjson

通常來講,我建議使用 SEQUENCE 策略,由於它容許 Hibernate 使用 JDBC 批處理和其餘須要延遲執行 SQL INSERT 語句的優化策略。ide

然而,你不能將此策略與 MySQL 數據庫一塊兒使用。由於它須要一個數據庫序列,剛好 MySQL 不支持此功能。函數

所以,你須要在 IDENTITYTABLE 之間進行選擇。考慮到 TABLE 策略的性能和可擴展性問題,答案顯而易見。oop

若是你正在使用 MySQL 數據庫,則應始終使用 GenerationType.IDENTITY。它使用了有自增特性(autoincremented )的數據庫列,這是最有效的可用方法。你能夠經過使用 @GeneratedValue(strategy = GenerationType.IDENTITY) 註解主鍵屬性來執行此操做。

@Entity
public class Author {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", updatable = false, nullable = false)
    private Long id;
 
    ...
}

二、映射:Hibernate 5 中 GenerationType.AUTO 問題

當你使用 GenerationType.AUTO 時,Hibernate 會根據 Hibernate 方言生成策略。若是你須要支持多個數據庫,如下一種經常使用的方法。

@Entity
public class Author {
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", updatable = false, nullable = false)
    private Long id;
 
    ...
}

在舊版本中,Hibernate 爲 MySQL 數據庫選擇 GenerationType.IDENTITY。這是一個不錯的選擇。如以前所述,這是最有效的方法。

不幸的是此行爲在 Hibernate 5 中發生了改變,如今使用的是 GenerationType.TABLE,它使用數據庫表來生成主鍵。這種方法須要大量數據庫查詢和悲觀鎖來生成惟一值。

14:35:50,959 DEBUG [org.hibernate.SQL] - select next_val as id_val from hibernate_sequence for update
14:35:50,976 DEBUG [org.hibernate.SQL] - update hibernate_sequence set next_val= ? where next_val=?
14:35:51,097 DEBUG [org.hibernate.SQL] - insert into Author (firstName, lastName, version, id) values (?, ?, ?, ?)

你能夠經過定義一個 @GenericGenerator 來避免這一點,如下代碼告訴 Hibernate 使用本地策略生成主鍵值。

@Entity
public class Author {
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
    @GenericGenerator(name = "native", strategy = "native")
    @Column(name = "id", updatable = false, nullable = false)
    private Long id;
 
    ...
}

而後,Hibernate 將使用 MySQL 自增數據庫列來生成主鍵值。

14:41:34,255 DEBUG [org.hibernate.SQL] - insert into Author (firstName, lastName, version) values (?, ?, ?)
14:41:34,298 DEBUG [org.hibernate.id.IdentifierGeneratorHelper] - Natively generated identity: 1

三、映射:只讀視圖

使用 JPA 與 Hibernate,你能夠以任何數據庫表相同的方式來映射視圖,只要遵循 Hibernate 的命名約定便可。你只須要實現一個使用了 @Entity 註解的類,該類的屬性爲你想要映射的列。

若是視圖只讀,你應該使用 @Immutable 註解告訴 Hibernate,它將忽略對該實體的全部更改。

@Entity
@Immutable
public class BookView {
   
  @Id
  @Column(name = "id", updatable = false, nullable = false)
  private Long id;
 
  @Column(name = "version")
  private int version;
  
  @Column
  private String title;
  
  @Column
  @Temporal(TemporalType.DATE)
  private Date publishingDate;
  
  @Column
  private String authors;
   
  ...
   
}

四、查詢:MySQL特有的函數與數據類型

做爲一個數據庫,MySQL 使用一組自定義函數和數據類型來擴展 SQL 標準。其中一例子是 JSON 數據類型sysdate 函數

JPA 並不支持這些,但因爲有 Hibernate 的 MySQL 方言的支持,所以你可使用它們。

Query q = em.createQuery("SELECT a, sysdate() FROM Author a ");
List<Object[]> results = q.getResultList();

若是你發現有 Hibernate MySQL 方言不支持的函數或者數據類型,則可使用一個 AttributeConverter 將數據類型轉換爲受支持的數據類型,或使用 JPQL 函數來調用一個 JPQL 查詢中的任意函數。

但請記住,當使用數據庫特有函數或數據類型,你能夠將應用程序綁定到特定的數據庫。但若是須要支持不一樣的數據庫,你將須要更改應用程序的這部分代碼。

五、查詢:存儲過程

許多數據庫管理員喜歡在數據庫中使用存儲過程來執行繁重的數據操做。 大多數狀況下,這種方法比在 Java 代碼中執行相同的操做要快得多。

可是,大多數 Java 開發人員並不想使用存儲過程。固然,有一個說法是業務邏輯分佈在多個系統上,這使得它更加難以測試和理解。另外一個緣由是在 JPA 2.1 以前,該規範並無直接支持存儲過程調用。你必須使用原生查詢,總之感受很複雜。

知道 JPA 2.1 引入了 StoredProcedureQuery@NamedStoredProcedureQuery 纔有所改變。

@NamedStoredProcedureQuery

@NamedStoredProcedureQuery 註解容許你定義存儲過程調用,且可經過其名稱在業務代碼中引用它。如下代碼片斷展現了一個簡單示例,它定義了存儲過程 calculate 的調用,附帶了輸入參數 xy 以及輸出參數 sum

@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 的名稱提供給 EntityManagercreateNamedStoredProcedureQuery,以實例化一個新的 StoredProcedureQuery

StoredProcedureQuery query = em.createNamedStoredProcedureQuery("calculate");
query.setParameter("x", 1.23d);
query.setParameter("y", 4d);
query.execute();
Double sum = (Double) query.getOutputParameterValue("sum");

在代碼段中能夠看到,你能夠設置輸入參數的值,設置方式與設置 JPQL 查詢的綁定參數值同樣。你只需使用輸入參數的名稱和值來調用 StoredProcedureQuerysetParameter 方法便可。

StoredProcedureQuery

存儲過程調用的編程定義很是相似於前面示例中給出的基於註解的方法。你只需將要執行的存儲過程的名稱配合 EntityManager 來調用 createStoredProcedureQuery 便可。還有一個 StoredProcedureQuery 接口,你可使用它來註冊存儲過程的輸入參數和輸出參數。

StoredProcedureQuery query = em.createStoredProcedureQuery("calculate");
query.registerStoredProcedureParameter("x", Double.class, ParameterMode.IN);
query.registerStoredProcedureParameter("y", Double.class, ParameterMode.IN);
query.registerStoredProcedureParameter("sum", Double.class, ParameterMode.OUT);

這就是定義存儲過程調用所有須要作的事。以後,你能夠與 @NamedStoredProcedureQuery 同樣使用它。在執行存儲過程調用以前,須要設置輸入參數值。

query.setParameter("x", 1.23d);
query.setParameter("y", 4d);
query.execute();

六、總結

Hibernate 已經支持大多數 MySQL 特有的特性。可是,若是要建立一個輕便且性能良好的應用程序,你還須要記住以上事項。

特別是 Hibernate 5 中生成惟一主鍵值和 GenerationType.AUTO 行爲的改變,可能會在將應用程序部署到生產環境時產生意外的可擴展性問題。

相關文章
相關標籤/搜索