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

Spring Data JPA中EmbeddedId含自增字段,保存后无法获取ID值

复合主键中自动生成ID保存后无法获取的解决办法

我有一个Customer实体,主键是复合键,其中的id字段为自动生成值。实体类及嵌入式主键类代码如下:

@Entity
@AllArgsConstructor
@Builder
@Setter
@Getter
public class Customer {
 
  @EmbeddedId
  private CustomerId customerId;

  @Size(max = 255)
  @Column(name = "first_name")
  private String firstName;
}
@Embeddable
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class CustomerId implements Serializable {
  private static final long serialVersionUID = 8048935613697415032L;
  @NotNull
  @Column(name = "account_id", nullable = false)
  private Long accountId;

  @NotNull
  @Column(name = "id", nullable = false)
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
}

使用以下代码保存Customer时,数据库成功写入记录,但无法获取saved.getCustomerId().getId()的值(返回null),尝试过EmbeddedId和IdClass均无效,求解决办法:

@PostMapping
public ResponseEntity create(@RequestBody Customer sc,
                             @RequestParam long account_id) {
  sc.setCustomerId(CustomerId.builder().accountId(account_id).build());
  Customer saved = customerRepository.saveAndFlush(sc);
  return ResponseEntity.ok(saved);
}

问题原因

JPA规范中,嵌入式主键(@EmbeddedId)内的字段不支持直接使用@GeneratedValue,这是因为嵌入式主键被视为一个整体,JPA不会单独对其中的字段进行自动生成值的回填操作。

解决办法

方案1:拆分主键字段,使用@IdClass+单独标注自动生成ID

把自动生成的id从嵌入式主键中拆分出来,作为实体的单独主键字段,同时保留accountId作为另一个主键字段,使用@IdClass定义复合主键:

  1. 定义主键类CustomerId(移除@GeneratedValue):
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class CustomerId implements Serializable {
    private static final long serialVersionUID = 8048935613697415032L;
    private Long accountId;
    private Long id;
}
  1. 修改Customer实体类:
@Entity
@AllArgsConstructor
@Builder
@Setter
@Getter
@IdClass(CustomerId.class)
public class Customer {
    @Id
    @NotNull
    @Column(name = "account_id", nullable = false)
    private Long accountId;

    @Id
    @NotNull
    @Column(name = "id", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Size(max = 255)
    @Column(name = "first_name")
    private String firstName;
}
  1. 调整保存逻辑:
@PostMapping
public ResponseEntity create(@RequestBody Customer sc,
                             @RequestParam long account_id) {
    sc.setAccountId(account_id);
    // id由数据库自动生成,无需手动设置
    Customer saved = customerRepository.saveAndFlush(sc);
    return ResponseEntity.ok(saved);
}

方案2:使用@MapsId关联单独的自动生成字段(保留嵌入式主键结构)

如果希望保留嵌入式主键的结构,可以通过单独的字段承载自动生成值,再用@MapsId映射到嵌入式主键中:

  1. 修改CustomerId(移除@GeneratedValue):
@Embeddable
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class CustomerId implements Serializable {
    private static final long serialVersionUID = 8048935613697415032L;
    @NotNull
    @Column(name = "account_id", nullable = false)
    private Long accountId;

    @NotNull
    @Column(name = "id", nullable = false)
    private Long id;
}
  1. 修改Customer实体类:
@Entity
@AllArgsConstructor
@Builder
@Setter
@Getter
public class Customer {
    @EmbeddedId
    private CustomerId customerId;

    // 单独定义自动生成的id字段,映射到嵌入式主键的id属性
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", insertable = false, updatable = false)
    @MapsId("id")
    private Long generatedId;

    @Size(max = 255)
    @Column(name = "first_name")
    private String firstName;
}
  1. 调整保存逻辑:
@PostMapping
public ResponseEntity create(@RequestBody Customer sc,
                             @RequestParam long account_id) {
    sc.setCustomerId(CustomerId.builder().accountId(account_id).build());
    Customer saved = customerRepository.saveAndFlush(sc);
    // 此时saved.getCustomerId().getId()会被正确回填,也可直接获取saved.getGeneratedId()
    return ResponseEntity.ok(saved);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 00:55:13