如何用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
相关产品推荐
相关产品推荐

