Creation timestamp and last update timestamp with Hibernate and MySQL
对于某个Hibernate实体,我们需要存储其创建时间和上次更新时间。 你会怎么设计这个?
-
您将在数据库中使用哪些数据类型(假设MySQL,可能在与JVM不同的时区)? 数据类型是时区感知的吗?
-
您将在Java中使用哪些数据类型(
Date ,Calendar ,long ,...)? -
您将负责设置时间戳—数据库,ORM框架(Hibernate)或应用程序员?
-
您将使用哪些注释进行映射(例如
@Temporal )?
我不只是在寻找一个可行的解决方案,而是一个安全且设计良好的解决方案。
如果您使用的是JPA注释,则可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
或者您可以在类上使用
你可以使用
1 2 3 4 5 6 7 8 9 |
利用这篇文章中的资源以及从不同来源左右传递的信息,我带来了这个优雅的解决方案,创建了以下抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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(); } } |
并让所有实体扩展它,例如:
1 2 3 4 5 | @Entity @Table(name ="campaign") public class Campaign extends AbstractTimestampEntity implements Serializable { ... } |
您还可以使用拦截器来设置值
创建一个名为TimeStamped的接口,您的实体将实现该接口
1 2 3 4 5 6 |
定义拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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: Column 'created' cannot be null
要解决此问题,请将updatable = false添加到"created"属性的@Column注释中:
1 2 3 | @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 和java.lang.Long 都是不可变的&mdash;有效地通过值传递&mdash;与java.util.Date s不同;在调试其他人的代码时,我真的很生气,找到类似foo.getLastUpdate().setTime(System.currentTimeMillis()) 的东西。 -
ORM框架应负责自动填写数据。
-
我还没有对此进行过测试,但只考虑了我认为
@Temporal 将完成工作的文档;不确定我是否可以将@Version 用于此目的。@PrePersist 和@PreUpdate 是手动控制的好方法。将它添加到所有实体的图层超类型(公共基类),这是一个可爱的想法,前提是您确实需要为所有实体添加时间戳。
如果您使用的是Session API,则根据此答案,PrePersist和PreUpdate回调将不起作用。
我在我的代码中使用Hibernate Session的persist()方法,所以我能够完成这项工作的唯一方法是使用下面的代码并关注此博客文章(也在答案中发布)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | @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; } } |
现在还有@CreatedDate和@LastModifiedDate注释。
=> https://programmingmitra.blogspot.fr/2017/02/automatic-spring-data-jpa-auditing-saving-CreatedBy-createddate-lastmodifiedby-lastmodifieddate-automatically.html
(Spring框架)
以下代码为我工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package com.my.backend.models; import java.util.Date; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.MappedSuperclass; import com.fasterxml.jackson.annotation.JsonIgnore; import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; import lombok.Getter; import lombok.Setter; @MappedSuperclass @Getter @Setter public class BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) protected Integer id; @CreationTimestamp @ColumnDefault("CURRENT_TIMESTAMP") protected Date createdAt; @UpdateTimestamp @ColumnDefault("CURRENT_TIMESTAMP") protected Date updatedAt; } |
只是强调:
一个好方法是为所有实体建立一个公共基类。在此基类中,如果通常在所有实体(通用设计),创建和上次更新日期属性中命名,则可以拥有id属性。
对于创建日期,您只需保留java.util.Date属性。确保始终使用新的Date()初始化它。
对于最后一个更新字段,您可以使用Timestamp属性,您需要使用@Version映射它。使用此Annotation,Hibernate将自动更新该属性。请注意,Hibernate也将应用乐观锁定(这是一件好事)。
您可以考虑将时间存储为DateTime和UTC。我通常使用DateTime而不是Timestamp,因为MySql在保存和检索数据时将日期转换为UTC并返回本地时间。我宁愿在一个地方保留任何这种逻辑(业务层)。我确信还有其他情况下使用Timestamp是可取的。
作为JAVA中的数据类型,我强烈建议使用java.util.Date。使用Calendar时,我遇到了非常讨厌的时区问题。看到这个主题。
为了设置时间戳,我建议使用AOP方法,或者你可以简单地在表上使用触发器(实际上这是我发现使用触发器的唯一可接受的东西)。
我们有类似的情况。 我们使用的是Mysql 5.7。
1 2 3 4 | CREATE TABLE my_table ( ... updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); |
这对我们有用。