1. 概述

本文将深入探讨 Hibernate 6.5 引入的 ON CONFLICT 子句,这是处理插入操作冲突的利器。当使用 HQL 或 Criteria 查询插入数据时,ON CONFLICT 子句能有效解决表约束冲突问题,同时还能优雅地实现 upsert(更新或插入)操作。

⚠️ 本文假设读者已熟悉 Hibernate 基础操作,JPA/Hibernate 入门知识将直接跳过。

2. ON CONFLICT 子句详解

2.1 语法结构

ON CONFLICT 子句的基本语法如下:

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

其中 conflictClause 的写法是:

"on conflict" conflictTarget? conflictAction

冲突动作 (conflictAction) 只有两种选择:

  • DO NOTHING:忽略冲突
  • DO UPDATE:更新冲突记录

2.2 实战示例

Student 实体类为例:

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

这里 studentId 是唯一键。处理冲突时,可以基于唯一约束名称或属性列表。踩坑提醒:使用约束名称需要数据库原生支持或单行插入。

2.2.1 DO UPDATE 更新冲突

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

核心机制:当插入的 studentId 已存在时,会更新现有记录。特殊别名 excluded 指代因冲突而插入失败的新值。

Hibernate 会将其转换为 MERGE 查询:

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) 

2.2.2 DO NOTHING 忽略冲突

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

冲突处理:当 studentId=1 已存在时,Hibernate 会静默忽略插入操作,避免抛出 ConstraintViolationException

3. 实战场景分析

3.1 DO UPDATE 场景

场景1:无冲突插入

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

结果分析

  • 无冲突时,ON CONFLICT 子句被忽略
  • 返回值 updated=1 表示成功插入
  • 数据库行数增加 1

场景2:冲突更新

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

结果分析

  • 冲突时触发更新操作(而非抛异常)
  • excluded.name 引用新值 'Sean'
  • 返回值 updated=1 表示成功更新
  • 数据库行数不变

3.2 DO NOTHING 场景

场景1:无冲突插入

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

结果分析

  • 无冲突时正常插入
  • 返回值 updated=1 表示成功插入
  • 数据库行数增加 1

场景2:冲突忽略

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

结果分析

  • 冲突时完全忽略操作
  • 返回值 updated=0 表示无记录受影响
  • 数据库行数不变

4. 总结

Hibernate 6.5 的 ON CONFLICT 子句提供了简单粗暴的冲突解决方案:

核心优势

  • 声明式处理唯一约束冲突
  • 无缝支持 upsert 操作
  • 避免 ConstraintViolationException 异常

⚠️ 使用建议

  • 优先使用 DO UPDATE 实现数据同步
  • 使用 DO NOTHING 避免重复插入错误
  • 注意 excluded 别名的作用域限制

完整示例代码已上传至 GitHub,建议结合实际业务场景测试验证。


原始标题:ON CONFLICT Clause for Hibernate Insert Queries | Baeldung