https://www.thoughts-on-java.org/5-things-you-need-to-know-when-using-hibernate-with-mysql/
作者:Thorben Janssen
譯者:oopsguy.com
使用 JPA 和 Hibernate 的其一好處是它提供了數據庫特定方言和功能抽象。因此,理論上,你可以實現一個應用程序,將其連接到一個受支持的數據庫,並且它可以在不用更改任何代碼的情況下運行。
Hibernate 真的很好。但老實說,你有沒有想過自己的應用程序真的能與每個支持的數據庫完美運行嗎?
Hibernate 幫你做了很多重要的事。但是,如果你希望應用程序能夠穩定運行,你仍需要知道要使用哪種數據庫,並相應地調整配置和代碼。
在之前的一篇文章中,我討論了“使用 Hibernate 和 PostgreSQL 數據庫需要知道的 6 件事情”。今天我想仔細討論 MySQL 數據庫。
1、映射:主鍵
處理和創建主鍵是很基礎的內容,但也是應用程序最重要的一部分。
JPA 規范的 @GeneratedValue 注解允許你定義用於創建唯一主鍵值的策略。你可以選擇 SEQUENCE、IDENTITY、TABLE 和 AUTO。
一般來說,我建議使用 SEQUENCE 策略,因為它允許 Hibernate 使用 JDBC 批處理和其他需要延遲執行 SQL INSERT 語句的優化策略。
然而,你不能將此策略與 MySQL 數據庫一起使用。因為它需要一個數據庫序列,恰好 MySQL 不支持此功能。
因此,你需要在 IDENTITY 和 TABLE 之間進行選擇。考慮到 TABLE 策略的性能和可擴展性問題,答案顯而易見。
如果你正在使用 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;
...
}
2、映射: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
3、映射:只讀視圖
使用 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;
...
}
4、查詢: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 查詢中的任意函數。
但請記住,當使用數據庫特有函數或數據類型,你可以將應用程序綁定到特定的數據庫。但如果需要支持不同的數據庫,你將需要更改應用程序的這部分代碼。
5、查詢:存儲過程
許多數據庫管理員喜歡在數據庫中使用存儲過程來執行繁重的數據操作。 大多數情況下,這種方法比在 Java 代碼中執行相同的操作要快得多。
但是,大多數 Java 開發人員並不想使用存儲過程。當然,有一個說法是業務邏輯分布在多個系統上,這使得它更加難以測試和理解。另一個原因是在 JPA 2.1 之前,該規范並沒有直接支持存儲過程調用。你必須使用原生查詢,總之感覺很復雜。
知道 JPA 2.1 引入了 StoredProcedureQuery 和 @NamedStoredProcedureQuery 才有所改變。
@NamedStoredProcedureQuery
@NamedStoredProcedureQuery 注解允許你定義存儲過程調用,且可通過其名稱在業務代碼中引用它。以下代碼片段展示了一個簡單示例,它定義了存儲過程 calculate 的調用,附帶了輸入參數 x 和 y 以及輸出參數 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 的名稱提供給 EntityManager 的 createNamedStoredProcedureQuery,以實例化一個新的 StoredProcedureQuery。
StoredProcedureQuery query = em.createNamedStoredProcedureQuery("calculate");
query.setParameter("x", 1.23d);
query.setParameter("y", 4d);
query.execute();
Double sum = (Double) query.getOutputParameterValue("sum");
在代碼段中可以看到,你可以設置輸入參數的值,設置方式與設置 JPQL 查詢的綁定參數值一樣。你只需使用輸入參數的名稱和值來調用 StoredProcedureQuery 的 setParameter 方法即可。
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();
6、總結
Hibernate 已經支持大多數 MySQL 特有的特性。但是,如果要創建一個輕便且性能良好的應用程序,你還需要記住以上事項。
特別是 Hibernate 5 中生成唯一主鍵值和 GenerationType.AUTO 行為的改變,可能會在將應用程序部署到生產環境時產生意外的可擴展性問題。
