Spring Data JPA 中的 @DynamicInsert 指南
1. 概述
Spring Data JPA 中的@DynamicInsert
註解透過在 SQL 語句中僅包含非空欄位來最佳化插入操作。此過程加快了產生的查詢速度,減少了不必要的資料庫互動。雖然它提高了具有許多可為空字段的實體的效率,但它引入了一些運行時開銷。因此,在排除null
列的好處超過效能成本的情況下,我們應該選擇性地使用它。
2. JPA 中的預設插入行為
當使用EntityManager
或 Spring Data JPA 的save()
方法持久化 JPA 實體時,Hibernate 會產生 SQL 插入語句。該語句包括每個實體列,即使某些列包含null
值。因此,在處理包含許多可選欄位的大型實體時,插入操作可能效率低。
現在讓我們來看看這個保存的實際效果。我們先考慮一個簡單的Account
實體:
@Entity
public class Account {
@Id
private int id;
@Column
private String name;
@Column
private String type;
@Column
private boolean active;
@Column
private String description;
// getters, setters, constructors
}
我們也會為Account
實體建立一個 JPA 儲存庫:
@Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {}
在這種情況下,當我們儲存一個新的Account
物件時:
Account account = new Account();
account.setId(ACCOUNT_ID);
account.setName("account1");
account.setActive(true);
accountRepository.save(account);
Hibernate 將產生一個包含所有欄位的 SQL 插入語句,即使只有少數欄位具有非空值:
insert into Account (active,description,name,type,id) values (?,?,?,?,?)
此行為並不總是最佳的,特別是對於某些欄位可能為null
或僅稍後初始化的大型實體。
3.使用@DynamicInsert
我們可以在實體層級使用@DynamicInsert
註解來優化此插入行為。應用時,Hibernate 將產生一條 SQL 插入語句,該語句僅包含具有非空值的列,從而避免 SQL 查詢中不必要的列。
讓我們將@DynamicInsert
註解新增至Account
實體:
@Entity
@DynamicInsert
public class Account {
@Id
private int id;
@Column
private String name;
@Column
private String type;
@Column
private boolean active;
@Column
private String description;
// getters, setters, constructors
}
現在,當我們儲存一個新的Account
實體並將某些欄位設為 null 時,Hibernate 將產生最佳化的 SQL 語句:
Account account = new Account();
account.setId(ACCOUNT_ID);
account.setName("account1");
account.setActive(true);
accountRepository.save(account);
產生的 SQL 將僅包含非空白列:
insert into Account (active,name,id) values (?,?,?)
4. @DynamicInsert
工作原理
在 Hibernate 級別, @DynamicInsert
影響框架如何產生和執行 SQL 插入語句。預設情況下,Hibernate 預先產生並快取包含每個映射列的靜態 SQL 插入語句,即使某些列在持久化實體時包含null
值。這是 Hibernate 效能最佳化的一部分,因為它重複使用了預先編譯的 SQL 語句,而無需每次都重新產生它們。
但是,當我們將@DynamicInsert
註解套用至實體時,Hibernate 會變更此行為並在執行時間動態產生插入 SQL 語句。
5. 何時使用@DynamicInsert
@DynamicInsert
註解是 Hibernate 中的一個強大功能,但其使用應根據具體場景選擇性應用。其中一種場景涉及具有許多可為空白欄位的實體。當實體具有可能不總是填充的欄位時, @DynamicInsert
會透過從 SQL 查詢中排除未設定的欄位來最佳化插入操作。這會減少產生的查詢的大小並提高效能。
當某些資料庫列具有預設值時,此註解也很有用。防止 Hibernate 插入null
值允許資料庫使用其預設設定處理這些欄位。例如,當插入記錄時, created_at
列可能會自動設定目前時間戳記。透過在插入語句中排除該字段,Hibernate 保留了資料庫的邏輯並防止覆寫預設行為。
此外, @DynamicInsert
在插入效能至關重要的場景中可能具有優勢。此註釋可確保僅將具有許多可能未全部填入的欄位的實體的相關資料傳送至資料庫。這在高效能係統中特別有用,在高效能係統中,最小化 SQL 語句的大小可以顯著影響效率。
6. 何時不使用@DynamicInsert
儘管正如我們所見,此註釋有很多好處,但在某些情況下它可能不是最佳選擇。我們不應該使用@DynamicInsert
的最具體情況是我們的實體大部分具有非空值的情況。在這種情況下,動態 SQL 產生可能會增加不必要的複雜性,但不會帶來重大影響。在這種情況下,由於大多數欄位是在插入操作期間填充的,因此@DynamicInsert
提供的最佳化變得多餘。
此外,不僅具有許多非空白欄位的表格不適合@DynamicInsert
,而且屬性很少的表格也不適合。對於簡單實體或只有幾個欄位的小表,使用@DynamicInsert
的優勢很小,並且排除null
值帶來的效能提升不太明顯。
在涉及批量插入的場景中, @DynamicInsert
的動態特性可能會導致效率低下。由於 Hibernate 為每個實體重新產生 SQL,而不是重複使用預先編譯的查詢,因此批次插入的執行效率可能不如靜態 SQL 插入。
在某些情況下, @DynamicInsert
可能無法很好地適應複雜的資料庫配置或模式,特別是在涉及複雜的約束或觸發器時。例如,如果架構具有需要某些列的特定值的約束,則@DynamicInsert
可能會忽略這些欄位(如果它們為null
,從而導致約束違規或錯誤。
假設我們的Account
實體附帶一個資料庫觸發器,如果文章類型為null
則插入「 UNKNOWN
」:
CREATE TRIGGER `account_type` BEFORE INSERT ON `account` FOR EACH ROW BEGIN
IF NEW.type IS NULL THEN
SET NEW.type = 'UNKNOWN';
END IF;
END
如果未提供類型,Hibernate 會完全排除該欄位。因此,觸發器可能無法啟動。
七、結論
在本文中,我們探索了@DynamicInsert
註解並透過程式碼範例了解了它的實際應用。我們研究了 Hibernate 如何動態產生僅包含非空列的 SQL 插入語句,透過避免 SQL 查詢中不必要的資料來最佳化效能。
我們還討論了優點,例如提高具有許多可為空字段的實體的效率以及更好地尊重資料庫預設值。然而,我們強調了它的局限性,包括插入null
值時的潛在開銷和複雜性。透過了解這些方面,我們可以就何時以及如何在應用程式中使用@DynamicInsert
做出明智的決定。
與往常一樣,本教程中使用的完整程式碼範例可在 GitHub 上取得。