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,建议结合实际业务场景测试验证。