JPA复合主键(外键+日期)设置日期自增及Java Play时间追踪类问题
嘿,我来帮你搞定这个Java Play项目里JPA复合主键日期字段自增的问题!首先得明确你说的“自增”大概率是两种场景:要么自动填充当前时间,要么在同一用户+项目组合下按特定规则递增日期。下面给你几个适配Play框架的实用方案:
方案1:自动填充当前时间(最常用场景)
如果你的需求是每次创建Tracking记录时,自动把主键里的日期字段设为当前系统时间,用JPA的生命周期注解@PrePersist就能轻松实现,完全不用依赖数据库特性。
首先假设你的TrackingPK嵌入式主键是这样的(补全你没写完的部分):
@Embeddable public class TrackingPK implements Serializable { private Long user; // 对应用户ID private Long project; // 对应项目ID private LocalDateTime date; // 要自动设置的日期字段 // 必须实现的equals、hashCode方法,以及getter/setter @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TrackingPK that = (TrackingPK) o; return Objects.equals(user, that.user) && Objects.equals(project, that.project) && Objects.equals(date, that.date); } @Override public int hashCode() { return Objects.hash(user, project, date); } // getter/setter 省略 }
然后在你的Tracking实体类里添加@PrePersist方法,在持久化前自动填充日期:
@Entity(name = "trackings") public class Tracking extends Model { @EmbeddedId public TrackingPK id; @ManyToOne @MapsId("user") @JoinColumn(name="user", referencedColumnName="id") public User user; @ManyToOne @MapsId("project") @JoinColumn(name="project", referencedColumnName="id") public Project project; @Required @Enumerated(EnumType.STRING) public ETrackingStatus status; // 补全你的枚举类型 // 持久化前自动设置日期 @PrePersist public void prePersist() { if (id == null) { id = new TrackingPK(); } // 同步用户和项目ID到主键 id.setUser(user.getId()); id.setProject(project.getId()); // 设置当前时间作为日期字段 id.setDate(LocalDateTime.now()); } // 其他业务方法省略 }
这样每次调用tracking.save()时,JPA会自动触发prePersist方法,帮你把日期字段设为当前时间,同时确保复合主键的完整性。
方案2:按自定义规则递增日期(同一用户&项目下)
如果你的需求是同一用户+项目组合下,日期按特定规则递增(比如每次比上一条记录晚1小时,或者按天递增且不重复),那需要先查询该组合的最新记录,再计算下一个日期。
在Tracking实体类里添加一个静态工厂方法来处理这个逻辑:
@Entity(name = "trackings") public class Tracking extends Model { // 现有字段省略... // 静态方法:创建带递增日期的Tracking实例 public static Tracking createWithIncrementedDate(User user, Project project) { Tracking tracking = new Tracking(); tracking.user = user; tracking.project = project; // 查询该用户+项目的最新记录 Tracking latestTracking = find.query() .where() .eq("user", user) .eq("project", project) .orderBy("id.date desc") .setMaxRows(1) .findOne(); LocalDateTime nextDate; if (latestTracking == null) { // 没有历史记录,用当前时间作为初始值 nextDate = LocalDateTime.now(); } else { // 这里自定义递增规则:比如每次递增1小时 nextDate = latestTracking.id.getDate().plusHours(1); // 如果是按天递增(确保当天无重复),可以替换成下面的逻辑: // LocalDateTime today = LocalDateTime.now().truncatedTo(ChronoUnit.DAYS); // if (latestTracking.id.getDate().truncatedTo(ChronoUnit.DAYS).isBefore(today)) { // nextDate = today; // } else { // nextDate = latestTracking.id.getDate().plusDays(1); // } } // 组装复合主键 TrackingPK pk = new TrackingPK(); pk.setUser(user.getId()); pk.setProject(project.getId()); pk.setDate(nextDate); tracking.id = pk; return tracking; } // 其他方法省略 }
使用的时候直接调用这个工厂方法:
// 获取当前用户和目标项目 User currentUser = ...; Project targetProject = ...; // 创建带递增日期的追踪记录 Tracking newTracking = Tracking.createWithIncrementedDate(currentUser, targetProject); newTracking.status = ETrackingStatus.IN_PROGRESS; // 设置枚举状态 newTracking.save();
方案3:依赖数据库默认值
如果你更倾向于让数据库来处理日期的自动生成,可以给TrackingPK的日期字段设置数据库层面的默认值(比如MySQL的CURRENT_TIMESTAMP):
@Embeddable public class TrackingPK implements Serializable { private Long user; private Long project; // 设置数据库列默认值为当前时间 @Column(name = "tracking_date", columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP") private LocalDateTime date; // equals、hashCode、getter/setter 省略 }
注意:这种方式下,JPA不会自动把数据库生成的日期同步到实体对象中,如果需要在save()后获取这个日期值,需要调用entityManager.refresh(tracking)来刷新实体(Play框架里可以通过JPA.em().refresh(tracking)实现)。
额外注意:复合主键的唯一性
不管用哪种方案,都要确保user+project+date的组合是唯一的,建议在Tracking实体类上添加唯一约束:
@Table(uniqueConstraints = { @UniqueConstraint(columnNames = {"user", "project", "tracking_date"}) }) @Entity(name = "trackings") public class Tracking extends Model { // 现有字段省略... }
内容的提问来源于stack exchange,提问作者Anthony PERIQUET

