对于某个Hibernate实体,我们需要存储其创建时间和上次更新时间.你会怎么设计这个?
您将在数据库中使用哪些数据类型(假设MySQL,可能在与JVM不同的时区)?数据类型是时区感知的吗?
你会在Java中使用什么数据类型(Date
,Calendar
,long
,...)?
您将负责设置时间戳 - 数据库,ORM框架(Hibernate)或应用程序员?
您将使用哪些注释进行映射(例如@Temporal
)?
我不只是在寻找一个可行的解决方案,而是一个安全且设计良好的解决方案.
如果您使用的是JPA注释,则可以使用@PrePersist
和@PreUpdate
事件挂钩执行此操作:
@Entity @Table(name = "entities") public class Entity { ... private Date created; private Date updated; @PrePersist protected void onCreate() { created = new Date(); } @PreUpdate protected void onUpdate() { updated = new Date(); } }
或者您可以@EntityListener
在类上使用注释并将事件代码放在外部类中.
你可以使用@CreationTimestamp
和@UpdateTimestamp
:
@CreationTimestamp @Temporal(TemporalType.TIMESTAMP) @Column(name = "create_date") private Date createDate; @UpdateTimestamp @Temporal(TemporalType.TIMESTAMP) @Column(name = "modify_date") private Date modifyDate;
利用这篇文章中的资源以及从不同来源左右传递的信息,我带来了这个优雅的解决方案,创建了以下抽象类
import java.util.Date; import javax.persistence.Column; import javax.persistence.MappedSuperclass; import javax.persistence.PrePersist; import javax.persistence.PreUpdate; import javax.persistence.Temporal; import javax.persistence.TemporalType; @MappedSuperclass public abstract class AbstractTimestampEntity { @Temporal(TemporalType.TIMESTAMP) @Column(name = "created", nullable = false) private Date created; @Temporal(TemporalType.TIMESTAMP) @Column(name = "updated", nullable = false) private Date updated; @PrePersist protected void onCreate() { updated = created = new Date(); } @PreUpdate protected void onUpdate() { updated = new Date(); } }
并让所有实体扩展它,例如:
@Entity @Table(name = "campaign") public class Campaign extends AbstractTimestampEntity implements Serializable { ... }
您还可以使用拦截器来设置值
创建一个名为TimeStamped的接口,您的实体将实现该接口
public interface TimeStamped { public Date getCreatedDate(); public void setCreatedDate(Date createdDate); public Date getLastUpdated(); public void setLastUpdated(Date lastUpdatedDate); }
定义拦截器
public class TimeStampInterceptor extends EmptyInterceptor { public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) { if (entity instanceof TimeStamped) { int indexOf = ArrayUtils.indexOf(propertyNames, "lastUpdated"); currentState[indexOf] = new Date(); return true; } return false; } public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { if (entity instanceof TimeStamped) { int indexOf = ArrayUtils.indexOf(propertyNames, "createdDate"); state[indexOf] = new Date(); return true; } return false; } }
并在会话工厂注册
使用Olivier的解决方案,在更新语句期间,您可能遇到:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:列'created'不能为null
要解决此问题,请将updatable = false添加到"created"属性的@Column注释中:
@Temporal(TemporalType.TIMESTAMP) @Column(name = "created", nullable = false, updatable=false) private Date created;
谢谢所有帮助过的人.在自己做了一些研究之后(我就是问过这个问题的人),这是我发现最有意义的事情:
数据库列类型:自1970年以来与时区无关的毫秒数表示为decimal(20)
因为2 ^ 64有20位数且磁盘空间便宜; 让我们直截了当.此外,我将既不使用也不使用DEFAULT CURRENT_TIMESTAMP
触发器.我想在DB中没有魔力.
Java字段类型:long
.Unix时间戳在各种库中得到很好的支持,long
没有Y2038问题,时间戳算法快速而简单(主要是运算符<
和运算符+
,假设计算中没有涉及天/月/年).并且,最重要的是,原始long
s和java.lang.Long
s都是不可变的 -有效地通过值传递 - 与java.util.Date
s 不同; foo.getLastUpdate().setTime(System.currentTimeMillis())
在调试其他人的代码时,我真的很生气.
ORM框架应负责自动填写数据.
我还没有测试过这个,但只看了我认为@Temporal
可以完成工作的文档; 不确定我是否可以@Version
用于此目的. @PrePersist
并且@PreUpdate
是手动控制的好方法.将其添加到所有实体的图层超类型(公共基类),这是一个可爱的想法,前提是您确实需要为所有实体设置时间戳.
如果您使用的是Session API,则根据此答案,PrePersist和PreUpdate回调将不起作用.
我在我的代码中使用Hibernate Session的persist()方法,所以我能够完成这项工作的唯一方法是使用下面的代码并关注此博客文章(也在答案中发布).
@MappedSuperclass public abstract class AbstractTimestampEntity { @Temporal(TemporalType.TIMESTAMP) @Column(name = "created") private Date created=new Date(); @Temporal(TemporalType.TIMESTAMP) @Column(name = "updated") @Version private Date updated; public Date getCreated() { return created; } public void setCreated(Date created) { this.created = created; } public Date getUpdated() { return updated; } public void setUpdated(Date updated) { this.updated = updated; } }