在Hibernate/Spring Data事务中使用GenerationType.IDENTITY的问题及触发器处理
解决Hibernate/Spring Data中GenerationType.IDENTITY搭配INSTEAD OF INSERT触发器的ID冲突问题
问题背景
你所有数据库表都用IDENTITY列做主键,必须采用GenerationType.IDENTITY生成策略。单实体保存时流程正常,但同一事务中创建两个同类型实体并调用save()时,会抛出DataIntegrityViolationException,提示“同一个会话中已关联了具有相同标识符值的不同对象”——根源是两个实体的ID都被设为0,Hibernate无法区分。
后续排查发现问题出在表上的INSTEAD OF INSERT触发器:这个触发器会拦截原始插入请求,自行计算字段值后执行插入,但没有把数据库生成的IDENTITY值返回给Hibernate。
核心原因
默认情况下,使用GenerationType.IDENTITY时,Hibernate执行INSERT语句后会立即通过SCOPE_IDENTITY()获取数据库生成的ID,然后更新实体的id字段,将实体转为托管状态。但INSTEAD OF INSERT触发器会替换原始的INSERT操作,此时原始INSERT不会返回ID,Hibernate拿不到新生成的ID,实体的id还是默认的0。当保存第二个实体时,Session里已经有一个id为0的托管实体,自然触发冲突异常。
解决办法:修改触发器返回生成的IDENTITY值
只需要修改触发器,在插入完成后把生成的ID返回给Hibernate即可。具体实现:
ALTER trigger [dbo].[Customer] on [dbo].[Customer] instead of insert as if @@rowcount = 0 return set nocount on Set dateformat dmy -- 临时表存储插入后生成的ID DECLARE @GeneratedIds TABLE (Id INT); Insert into dbo.Customer (f1, f2, ....) OUTPUT inserted.Id INTO @GeneratedIds -- 将生成的ID存入临时表 Select I.f1, isnull(f2,newid()), ... from Inserted I -- 返回ID供Hibernate捕获 SELECT Id FROM @GeneratedIds;
额外注意事项
- 确保实体id字段类型与数据库IDENTITY列类型匹配:你代码里实体id是
Long,但数据库列是int,建议改成Integer或者将数据库列调整为bigint,避免类型转换问题。 - 优先用
OUTPUT inserted.Id或SCOPE_IDENTITY()获取ID,不要用@@IDENTITY——后者会返回当前会话中所有触发器生成的最后一个ID,可能拿到错误值。
内容的提问来源于stack exchange,提问作者Andrei
相关产品推荐
相关产品推荐

