使用IdClass时无法分配自动生成值的技术问题求助
我有一个采用复合主键的Customer实体,复合主键中的id字段为自动生成值。实体类与主键类代码如下:
Customer 实体类
@Entity @AllArgsConstructor @Builder @Setter @Getter @IdClass(CustomerId.class) public class Customer { @NotNull @Column(name = "account_id", nullable = false) private Long accountId; @NotNull @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Size(max = 255) @Column(name = "first_name") private String firstName; }
CustomerId 主键类
@Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode public class CustomerId implements Serializable { private static final long serialVersionUID = 8048935613697415032L; private Long accountId; private Long id; }
当使用以下代码保存Customer时:
@PostMapping public ResponseEntity create(@RequestBody Customer sc, @RequestParam long account_id) { Customer saved = customerRepository.saveAndFlush(sc); return ResponseEntity.ok(saved); }
出现错误:
org.springframework.orm.jpa.JpaSystemException: Could not set field value [POST_INSERT_INDICATOR] value by reflection : [class com.demo.service.entity.core.CustomerId.id] setter of com.demo.service.entity.core.CusotmerId.id;
这个错误的核心是JPA的@IdClass机制不支持复合主键中包含自动生成字段。使用@IdClass时,JPA要求所有主键字段在持久化前必须有明确值,但@GeneratedValue(strategy = GenerationType.IDENTITY)是在数据插入数据库后才生成值,两者存在冲突。Hibernate处理时会尝试给主键类的id字段设置POST_INSERT_INDICATOR标记,但主键类的id字段类型为Long,无法接收该特殊标记,从而抛出反射赋值异常。
方案1:改用@EmbeddedId替代@IdClass
@EmbeddedId支持复合主键包含自动生成字段,调整代码如下:
- 修改主键类,添加
@Embeddable注解,并将@GeneratedValue移到主键类的id字段上:
@Embeddable @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode public class CustomerId implements Serializable { private static final long serialVersionUID = 8048935613697415032L; private Long accountId; @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; }
- 修改Customer实体类,替换
@IdClass为@EmbeddedId,并调整字段定义:
@Entity @AllArgsConstructor @Builder @Setter @Getter public class Customer { @EmbeddedId private CustomerId id; @Size(max = 255) @Column(name = "first_name") private String firstName; }
- 调整保存接口代码,正确设置复合主键的
accountId:
@PostMapping public ResponseEntity create(@RequestBody Customer sc, @RequestParam long account_id) { // 初始化主键对象并设置accountId if(sc.getId() == null) { sc.setId(new CustomerId()); } sc.getId().setAccountId(account_id); Customer saved = customerRepository.saveAndFlush(sc); return ResponseEntity.ok(saved); }
方案2:调整主键策略(需评估业务场景)
如果不想使用@EmbeddedId,可以重新设计主键:将accountId与某个非自动生成的业务字段作为复合主键,把id改为普通的自动生成字段(不再作为主键的一部分)。这种方式会改变实体的主键逻辑,需要结合业务场景判断是否适用。
内容的提问来源于stack exchange,提问作者Arul Pragasam

