Hibernate 插入查詢的 ON CONFLICT 子句
1. 概述
在本教程中,我們將了解 Hibernate 6.5 中引入的insert查詢的ON CONFLICT子句。
在使用 HQL 或條件查詢插入資料時,我們使用ON CONFLICT子句來處理表格約束違規。 ON CONFLICT子句也可用於處理 upsert 查詢。
要了解有關 Hibernate 中insert查詢的更多信息,請查看有關如何對 JPA 物件執行INSERT語句的教程。
2. ON CONFLICT條款
使用ON CONFLICT子句的insert語法為:
"INSERT" "INTO"? targetEntity targetFields (queryExpression | valuesList) conflictClause?
conflictClause寫為:
"on conflict" conflictTarget? conflictAction
conflictAction要不是DO NOTHING就是DO UPDATE 。
現在,讓我們來看一個例子。讓我們考慮具有studentId和name作為屬性的實體類別Student :
@Entity
public class Student {
@Id
private long studentId;
private String name;
}
studentId屬性是Student實體的唯一鍵。我們可以在INSERT VALUES查詢中插入@Id值,也可以使用@GeneratedValue註解。
對衝突的反應可以基於形成唯一約束的名稱或屬性清單。使用唯一約束名稱作為衝突目標需要本機資料庫支援或該語句是單行insert 。
可能的衝突操作是忽略衝突或更新衝突的物件/行:
int updated = session.createMutationQuery("""
insert into Student (studentId, name)
values (1, 'John')
on conflict(studentId) do update
set name = excluded.name
""").executeUpdate();
在這裡,如果插入與現有行具有相同studentId的行而發生衝突,則更新現有行。 excluded特殊別名可在ON CONFLICT子句的update set句中使用,它指的是由於唯一約束衝突而插入失敗的值。
Hibernate 將帶有ON CONFLICT子句的查詢轉換為合併查詢:
MERGE INTO Student s1_0
USING (VALUES (1, 'John')) excluded(studentId, NAME)
ON ( s1_0.studentId = excluded.studentId)
WHEN matched THEN
UPDATE SET NAME = excluded.NAME
WHEN NOT matched THEN
INSERT (studentId,
NAME)
VALUES (excluded.studentId,
excluded.NAME)
ON CONFLICT子句使用excluded功能轉換為 upsert 查詢.此查詢排除別名為p1_0原始記錄並插入新記錄。如果studentId (衝突屬性)匹配,那麼我們更新studentId以外的屬性.如果不匹配,那麼我們執行insert操作。
我們使用DO NOTHING來忽略衝突,確保發生衝突時不會發生任何事情並避免潛在的錯誤:
int updated = session.createMutationQuery("""
insert into Student (studentId, name)
values (1, 'John')
on conflict(studentId) do nothing
""").executeUpdate();
在這裡,如果表格已經包含studentId 1 的行,Hibernate 將忽略該查詢並避免異常。
3. 例子
3.1. DO UPDATE
我們加入了一些測試案例,以便更好地理解ON CONFLICT子句。我們插入具有衝突操作的非衝突資料do update :
long rowCountBefore = getRowCount();
int updated = session.createMutationQuery("""
insert into Student (studentId, name) values (2, 'Sean')
on conflict(studentId) do update
set name = excluded.name
""").executeUpdate();
long rowCountAfter = getRowCount();
assertEquals(updated, 1);
assertEquals(rowCountBefore, rowCountAfter);
插入的資料不衝突並且插入到資料庫中。當不存在衝突時,Hibernate會忽略ON CONFLICT子句,查詢執行傳回更新值1 。
現在,讓我們來看看一個測試案例,其中我們使用衝突操作do update插入衝突資料:
long rowCountBefore = getRowCount();
int updated = session.createMutationQuery("""
insert into Student (studentId, name) values (1, 'Sean')
on conflict(studentId) do update
set name = excluded.name
""").executeUpdate();
long rowCountAfter = getRowCount();
assertEquals(updated, 1);
assertEquals(rowCountBefore, rowCountAfter);
在這個**studentId ConstraintViolationException中,查詢插入了studentId studentId為1的記錄。我們使用ON CONFLICT子句處理此異常**。 do update更新指定的數據,而不是取得異常、指定的衝突操作。
行set name = excluded.name更新name欄位。關鍵字excluded帶有衝突操作do nothing 。我們可以使用excluded關鍵字更新除衝突欄位之外的所有欄位.
3.2. DO NOTHING
現在讓我們看看當我們使用ON CONFLICT子句插入非衝突資料並將衝突操作設定為do nothing操作時會發生什麼:
long rowCountBefore = getRowCount();
int updated = session.createMutationQuery("""
insert into Student (studentId, name) values (2, 'Sean')
on conflict do nothing
""").executeUpdate();
long rowCountAfter = getRowCount();
assertEquals(updated, 1);
assertNotEquals(rowCountBefore, rowCountAfter);
我們觀察到將資料記錄插入資料庫時沒有衝突。查詢傳回 1 作為更新值。查詢執行使表中的行數增加 1。
讓我們來看看插入衝突資料並將ON CONFLICT子句與do nothing操作的情況:
long rowCountBefore = getRowCount();
int updated = session.createMutationQuery("""
insert into Student (studentId, name) values (1, 'Sean')
on conflict do nothing
""").executeUpdate();
long rowCountAfter = getRowCount();
assertEquals(updated, 0);
assertEquals(rowCountBefore, rowCountAfter);
這裡插入的記錄的studentId為1,執行查詢時會產生衝突。由於我們使用了do nothing操作”,因此在發生衝突時它不會執行任何操作。查詢執行傳回更新值 0,但不更新任何記錄。此外,查詢執行前後的行數保持不變。
4. 結論
在本文中,我們了解了 Hibernate 6.5 中引入的ON CONFLICT子句。我們也透過範例來更好地理解ON CONFLICT子句。
與往常一樣,本文中使用的完整程式碼可以 在 GitHub 上找到。