用Hibernate和MySQL创build时间戳和上次更新时间戳
对于某个Hibernate实体,我们需要存储它的创build时间和最后一次更新的时间。 你将如何devise这个?
-
你会在数据库中使用哪些数据types(假设MySQL可能与JVM在不同的时区)? 数据types是否可以识别时区?
-
你会在Java中使用哪些数据types(
Date
,Calendar
,long
,…)? -
你会负责设置时间戳 – 数据库,ORM框架(Hibernate)还是应用程序员?
-
你会用什么注释来进行映射(例如
@Temporal
)?
我不仅在寻找一个可行的解决scheme,而且还需要一个安全和devise良好的解决scheme。
如果您使用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
注释,并将事件代码放在外部类中。
把这篇文章的资源和来自不同来源的信息左右排列起来,我就带着这个优雅的解决scheme,创build了下面的抽象类
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 { ... }
您可以使用@CreationTimestamp
和@UpdateTimestamp
:
@CreationTimestamp @Temporal(TemporalType.TIMESTAMP) @Column(name = "create_date") private Date createDate; @UpdateTimestamp @Temporal(TemporalType.TIMESTAMP) @Column(name = "modify_date") private Date modifyDate;
您也可以使用拦截器来设置值
创build您的实体实现的名为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; } }
并注册到会话工厂
感谢所有帮助过的人。 我自己做了一些研究(我是问这个问题的人),这里是我发现最有意义的东西:
-
数据库列types:自1970年以来的时区不可知的毫秒数,以
decimal(20)
表示,因为2 ^ 64有20个数字,磁盘空间便宜; 让我们直截了当 另外,我将既不使用DEFAULT CURRENT_TIMESTAMP
也不使用触发器。 我不想在DB中有魔法。 -
Java字段types:
long
。 Unix时间戳很好的支持各种库,long
没有Y2038的问题,时间戳algorithm是快速和容易的(主要是运营商<
和运营商+
,假设没有天/月/年计算)。 而且,最重要的是,原始long
和java.lang.Long
都是不可变的 – 与java.util.Date
不同,它有效地传递值 – 当debugging别人的代码的时候,我会非常生气地find类似foo.getLastUpdate().setTime(System.currentTimeMillis())
东西。 -
ORM框架应该负责自动填写数据。
-
我还没有testing过,但只看文档,我认为
@Temporal
将做的工作; 不知道我是否可以使用@Version
来达到这个目的。@PrePersist
和@PreUpdate
是手动控制的好select。 添加到所有实体的图层超types(公共基类),是一个可爱的想法,只要你真的想要所有的实体时间戳。
有了Olivier的解决scheme,在更新语句中,您可能会遇到:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:列'created'不能为空
为了解决这个问题,将updatable = false添加到“created”属性的@Column注释中:
@Temporal(TemporalType.TIMESTAMP) @Column(name = "created", nullable = false, updatable=false) private Date created;
如果您正在使用会话API,PrePersist和PreUpdatecallback将不会根据这个答案 。
我在我的代码中使用了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; } }
一个好的方法是为所有的实体设置一个通用的基类。 在这个基类中,如果你的id属性在你的所有实体(一个通用的devise)中共同命名,你的创build和上次更新date属性就可以拥有你的id属性。
对于创builddate,只需保留一个java.util.Date属性。 一定要用新的Date()来初始化它。
对于上次更新字段,您可以使用Timestamp属性,您需要使用@Version来映射它。 通过这个Annotation,这个属性将会被Hibernate自动更新。 请注意,Hibernate也将应用乐观locking(这是一件好事)。
只是为了加强: java.util.Calender
不适用于时间戳 。 java.util.Date
是一段时间,不区分时区等区域事物。 大多数数据库以这种方式存储事物(即使它们看起来不是这样;这通常是客户端软件中的时区设置;数据是好的)
作为JAVA中的数据types,我强烈build议使用java.util.Date。 当使用日历时,我遇到了非常讨厌的时区问题。 看到这个主题 。
为了设置时间戳,我build议使用AOP方法,或者你可以简单地在表上使用触发器(实际上,这是我唯一能够使用触发器的东西)。
您可能会考虑将时间存储为DateTime和UTC。 我通常使用DateTime而不是Timestamp,因为MySql将date转换为UTC,并在存储和检索数据时返回到本地时间。 我宁愿把这种逻辑放在一个地方(业务层)。 我敢肯定,还有其他的情况下使用时间戳是可取的。
具有本地MySQLfunction的解决scheme:使用MySQL 更新和创build时间戳可以很好地使用java和hibernate。
我们也有类似的情况。 我们正在使用Mysql 5.7。
CREATE TABLE my_table ( ... updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );
这对我们有效。