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

如何用JPA实现含时间戳复合主键的追加式实体?

复合主键(Varchar+时间戳)的JPA建模与追加式记录实现问题

问题说明

数据库表auth.permissions的主键是permissionid(Varchar)与createdat(时间戳)组成的复合键,要求每次修改或插入实体时都向数据库新增一条记录,且不能修改数据库Schema。当前使用JPA的@IdClass建模时,创建新实体报错createdAt不能为空,已通过原生SQL实现临时解决方案,希望找到标准JPA实现方式。

当前实现代码

实体类

@Entity
@Table(name = "permissions", schema = "auth")
@IdClass(PermissionId.class)
public class Permission implements Serializable {

  @Id
  @Column(name = "permissionid")
  private String permissionId;

  @Id
  @Column(name = "createdat")
  @UpdateTimestamp
  private Instant createdAt;
  // 其他字段省略
}

复合主键类

public class PermissionId implements Serializable {
    private String permissionId;
    private Instant createdAt;
}

临时解决方案(原生SQL)

@Query(
        value = "INSERT INTO auth.permissions (permissionID, createdAt,...) VALUES (:permissionID, NOW(), ...)",
        nativeQuery = true
)
@Modifying
void recordPermission(String permissionID, ...);

标准JPA实现方案

核心思路:利用@PrePersist自动生成主键时间戳

@UpdateTimestamp通常用于更新操作时自动更新时间,而主键字段在插入时必须有有效值。通过@PrePersist注解在实体持久化前自动赋值createdAt,即可解决空值问题,同时满足追加式记录的要求。

修改后的实体类

@Entity
@Table(name = "permissions", schema = "auth")
@IdClass(PermissionId.class)
public class Permission implements Serializable {

  @Id
  @Column(name = "permissionid")
  private String permissionId;

  @Id
  @Column(name = "createdat")
  private Instant createdAt;

  // 其他业务字段...

  /**
   * 持久化前自动设置时间戳
   */
  @PrePersist
  public void prePersist() {
      if (this.createdAt == null) {
          this.createdAt = Instant.now();
      }
  }

  // 生成getter、setter
}

完善复合主键类

JPA要求复合主键类必须实现equals和hashCode方法,同时提供无参构造器,否则无法正确识别主键唯一性:

import java.time.Instant;
import java.util.Objects;

public class PermissionId implements Serializable {
    private String permissionId;
    private Instant createdAt;

    // JPA要求的无参构造器
    public PermissionId() {}

    // 带参构造器方便实例化
    public PermissionId(String permissionId, Instant createdAt) {
        this.permissionId = permissionId;
        this.createdAt = createdAt;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PermissionId that = (PermissionId) o;
        return Objects.equals(permissionId, that.permissionId) && Objects.equals(createdAt, that.createdAt);
    }

    @Override
    public int hashCode() {
        return Objects.hash(permissionId, createdAt);
    }

    // 生成getter、setter
    public String getPermissionId() { return permissionId; }
    public void setPermissionId(String permissionId) { this.permissionId = permissionId; }
    public Instant getCreatedAt() { return createdAt; }
    public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; }
}

使用方式

每次需要新增或更新记录时,创建全新的Permission实例,设置permissionId和其他业务字段后,调用JPA Repository的save()方法即可。由于每次生成的createdAt不同,复合主键(permissionId, createdAt)始终唯一,JPA会自动执行插入操作,不会覆盖原有数据,完美实现追加式记录的需求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 19:15:39