51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Hibernate 中 INSERT 查询的 ON CONFLICT 子句

1、概览 {#1概览}

本文将带你了解 Hibernate 6.5 中引入的用于 INSERT 查询的 ON CONFLICT 子句。

我们使用 ON CONFLICT 子句来处理使用 HQLCriteria 查询插入数据时违反表约束的情况。ON CONFLICT 子句也可以用于处理 upsert 查询。

2、ON CONFLICT 子句 {#2on-conflict-子句}

使用 ON CONFLICT 子句进行 insert 的语法如下:

"INSERT" "INTO"? targetEntity targetFields (queryExpression | valuesList) conflictClause?

conflictClause 的写法为:

"on conflict" conflictTarget? conflictAction

conflictAction (冲突操作)可以是 DO NOTHING 或者 DO UPDATE

现在,来看一个例子。假设实体类 Student 的属性有 studentIdname

@Entity
public class Student {
    @Id
    private long studentId;
    private String name;
}

studentId 属性是 Student 实体的唯一键。我们可以在 INSERT VALUES 查询中手动插入 @Id 值,或者使用 @GeneratedValue 注解来指定 ID 的生成策略。

可以根据唯一约束的名称或属性列表来处理冲突。要使用唯一约束名称作为冲突目标,需要数据库的原生支持,或者操作是单行 insert 插入。

冲突处理的操作包括 忽略冲突更新冲突 的对象/行:

int updated = session.createMutationQuery("""
  insert into Student (studentId, name)
  values (1, 'John')
  on conflict(studentId) do update
  set name = excluded.name
  """).executeUpdate();

如上,如果插入一条与现有记录具有相同 studentId 的记录发生冲突,那么现有记录将被更新。特殊别名 excludedON CONFLICT 子句的 update set 子句中可用,指的是由于唯一约束冲突而插入失败的值。

Hibernate 会将带有 ON CONFLICT 子句的查询转换为 Merge Query(合并查询):

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) 

使用 excluded 功能将 ON CONFLICT 子句转换为 upsert 查询。该查询排除了别名为 p1_0 的原始记录,并插入了新的记录。如果 studentId(冲突属性)匹配,则更新 studentId 以外的属性。如果不匹配,则执行插入操作。

我们使用 DO NOTHING 来忽略冲突,确保在发生冲突时不会发生任何事情,避免潜在错误:

int updated = session.createMutationQuery("""
  insert into Student (studentId, name)
  values (1, 'John')
  on conflict(studentId) do nothing
  """).executeUpdate();

如上,如果表中已经包含了一条 studentId1 的记录,Hibernate 会忽略查询并避免异常。

3、示例 {#3示例}

3.1、DO UPDATE {#31do-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。执行语句后,行数量发生变化,表明查询在表中插入了一行。

现在,来看一个测试用例,在这个测试用例中,我们插入了冲突数据,并通过 冲突操作 进行了更新:

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);

在这个测试案例中,查询插入了一条 studentId1 的记录,而表中已经有一条 studentId1 的记录。 如果执行查询,通常会抛出 ConstraintViolationException 异常,因为 studentId 是唯一约束。我们使用 ON CONFLICT 子句来处理这个异常。指定的冲突操作不会产生异常,而是会更新指定的数据。

set name = excluded.name 这一行更新了 name 字段。关键字 excluded 自带冲突操作,什么也不做。

我们可以使用关键字 excluded 更新冲突字段以外的所有字段。

3.2、DO NOTHING {#32do-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);

如上,插入记录的 studentId1 ,因此在执行 insert 查询时会产生冲突。由于我们使用了 "do nothing"(什么都不做)操作,因此在发生冲突时不会执行任何操作。查询执行后会返回更新值 0,表示未更新任何记录。此外,查询执行前后的记录数量保持不变。

4、总结 {#4总结}

本文介绍了 Hibernate 6.5 中引入的用于 INSERT 查询的 ON CONFLICT 子句,以处理使用 HQL 或 Criteria 查询插入数据时违反表约束的情况。


Ref:https://www.baeldung.com/hibernate-insert-query-on-conflict-clause

赞(2)
未经允许不得转载:工具盒子 » Hibernate 中 INSERT 查询的 ON CONFLICT 子句