Creation timestamp and last update timestamp with Hibernate and MySQL

匿名 (未验证) 提交于 2019-12-03 01:18:02

问题:

For a certain Hibernate entity we have a requirement to store its creation time and the last time it was updated. How would you design this?

  • What data types would you use in the database (assuming MySQL, possibly in a different timezone that the JVM)? Will the data types be timezone-aware?

  • What data types would you use in Java (Date, Calendar, long, ...)?

  • Whom would you make responsible for setting the timestamps―the database, the ORM framework (Hibernate), or the application programmer?

  • What annotations would you use for the mapping (e.g. @Temporal)?

I'm not only looking for a working solution, but for a safe and well-designed solution.

回答1:

If you are using the JPA annotations, you can use @PrePersist and @PreUpdate event hooks do this:

@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();   } } 

or you can use the @EntityListener annotation on the class and place the event code in an external class.



回答2:

Taking the resources in this post along with information taken left and right from different sources, I came with this elegant solution, create the following abstract class

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();     } } 

and have all your entities extend it, for instance:

@Entity @Table(name = "campaign") public class Campaign extends AbstractTimestampEntity implements Serializable { ... } 


回答3:

You can just use @CreationTimestamp and @UpdateTimestamp:

@CreationTimestamp @Temporal(TemporalType.TIMESTAMP) @Column(name = "create_date") private Date createDate;  @UpdateTimestamp @Temporal(TemporalType.TIMESTAMP) @Column(name = "modify_date") private Date modifyDate; 


回答4:

You can also use an interceptor to set the values

Create an interface called TimeStamped which your entities implement

public interface TimeStamped {     public Date getCreatedDate();     public void setCreatedDate(Date createdDate);     public Date getLastUpdated();     public void setLastUpdated(Date lastUpdatedDate); } 

Define the interceptor

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;     } } 

And register it with the session factory



回答5:

Thanks everyone who helped. After doing some research myself (I'm the guy who asked the question), here is what I found to make sense most:

  • Database column type: the timezone-agnostic number of milliseconds since 1970 represented as decimal(20) because 2^64 has 20 digits and disk space is cheap; let's be straightforward. Also, I will use neither DEFAULT CURRENT_TIMESTAMP, nor triggers. I want no magic in the DB.

  • Java field type: long. The Unix timestamp is well supported across various libs, long has no Y2038 problems, timestamp arithmetic is fast and easy (mainly operator and operator +, assuming no days/months/years are involved in the calculations). And, most importantly, both primitive longs and java.lang.Longs are immutable―effectively passed by value―unlike java.util.Dates; I'd be really pissed off to find something like foo.getLastUpdate().setTime(System.currentTimeMillis()) when debugging somebody else's code.

  • The ORM framework should be responsible for filling in the data automatically.

  • I haven't tested this yet, but only looking at the docs I assume that @Temporal will do the job; not sure about whether I might use @Version for this purpose. @PrePersist and @PreUpdate are good alternatives to control that manually. Adding that to the layer supertype (common base class) for all entities, is a cute idea provided that you really want timestamping for all of your entities.



回答6:

With Olivier's solution, during update statements you may run into:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'created' cannot be null

To solve this, add updatable=false to the @Column annotation of "created" attribute:

@Temporal(TemporalType.TIMESTAMP) @Column(name = "created", nullable = false, updatable=false) private Date created; 


回答7:

In case you are using the Session API the PrePersist and PreUpdate callbacks won't work according to this answer.

I am using Hibernate Session's persist() method in my code so the only way I could make this work was with the code below and following this blog post (also posted in the answer).

@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;     } } 


回答8:

A good approach is to have a common base class for all your entities. In this base class, you can have your id property if it is commonly named in all your entities (a common design), your creation and last update date properties.

For the creation date, you simply keep a java.util.Date property. Be sure, to always initialize it with new Date().

For the last update field, you can use a Timestamp property, you need to map it with @Version. With this Annotation the property will get updated automatically by Hibernate. Beware that Hibernate will also apply optimistic locking (it's a good thing).



回答9:

Just to reinforce: java.util.Calender is not for Timestamps. java.util.Date is for a moment in time, agnostic of regional things like timezones. Most database store things in this fashion (even if they appear not to; this is usually a timezone setting in the client software; the data is good)



回答10:

As data type in JAVA I strongly recommend to use java.util.Date. I ran into pretty nasty timezone problems when using Calendar. See this Thread.

For setting the timestamps I would recommend using either an AOP approach or you could simply use Triggers on the table (actually this is the only thing that I ever find the use of triggers acceptable).



回答11:

You might consider storing the time as a DateTime, and in UTC. I typically use DateTime instead of Timestamp because of the fact that MySql converts dates to UTC and back to local time when storing and retrieving the data. I'd rather keep any of that kind of logic in one place (Business layer). I'm sure there are other situations where using Timestamp is preferable though.



回答12:

Solution with native MySQL functionality: Update and create timestamps with MySQL Works fine with java and hibernate.



回答13:

We had a similar situation. We were using Mysql 5.7.

CREATE TABLE my_table (         ...       updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP     ); 

This worked for us.



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!