You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot升级后JpaSystemException:实体ID被修改异常求助

问题分析与解决方案

报错原因

Spring Boot 3.2.x 依赖的 Hibernate 6.x 对复合实体ID的变更检测逻辑比 Spring Boot 2.7.x 对应的 Hibernate 5.x 更严格。你的场景存在核心矛盾:

  1. createdAt 作为复合ID的一部分,你通过 @PrePersist 手动赋值;
  2. 同时给该字段标注了 @Column(insertable=false),这会让 Hibernate 判定该字段为数据库生成字段,不会将其纳入 INSERT 语句;
  3. 插入完成后,Hibernate 会尝试从数据库读取该字段的“生成值”,但由于 INSERT 时未传入值,数据库要么返回 null(无默认值),要么返回数据库默认值,导致内存中实体的ID(含 @PrePersist 设置的 createdAt)与数据库返回的ID不一致,触发“ID被修改”的异常。

而在 Spring Boot 2.7.x + Hibernate 5.x 中,这种矛盾未被严格检测,因此测试可以正常执行。

解决方案

方案一:移除 insertable=false(推荐)

既然你通过 @PrePersist 主动设置 createdAt 的值,就需要让 Hibernate 将该字段包含在 INSERT 语句中,因此移除 insertable=false:

@Entity
@Table(name = "workloads")
@IdClass(WorkloadId.class)
public class WorkloadDAO {

  @Id
  @NonNull
  private String id;
  
  @Id
  @Column(name = "created_at", updatable = false, columnDefinition = "TIMESTAMP")
  private LocalDateTime createdAt;

  // 更多属性

  @PrePersist
  protected void onCreate() {
    createdAt = LocalDateTime.now();
  }
}

修改后,Hibernate 会将 created_at 字段插入数据库,插入后不会重置该值,实体ID保持一致,测试即可正常通过。

方案二:改用数据库自动生成值

如果希望由数据库自动生成 createdAt(比如使用 CURRENT_TIMESTAMP),可以去掉 @PrePersist 逻辑,让数据库处理默认值:

@Entity
@Table(name = "workloads")
@IdClass(WorkloadId.class)
public class WorkloadDAO {

  @Id
  @NonNull
  private String id;
  
  @Id
  @Column(name = "created_at", insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
  private LocalDateTime createdAt;

  // 更多属性,移除@PrePersist方法
}

此时数据库会自动设置 created_at 的值,Hibernate 在插入后会自动读取该值,实体ID不会出现变更,测试也能正常运行。

测试验证

两种方案都不需要修改你的测试代码,只要实体逻辑调整正确,saveAndFlush 返回的 response 会包含有效的 createdAt 值,用其构建 WorkloadId 即可成功查询到实体。

内容的提问来源于stack exchange,提问作者Fernando

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 00:23:12