关于java:使用Hibernate和MySQL创建时间戳和上次更新时间戳

Creation timestamp and last update timestamp with Hibernate and MySQL

对于某个Hibernate实体,我们需要存储其创建时间和上次更新时间。 你会怎么设计这个?

  • 您将在数据库中使用哪些数据类型(假设MySQL,可能在与JVM不同的时区)? 数据类型是时区感知的吗?

  • 您将在Java中使用哪些数据类型(DateCalendarlong,...)?

  • 您将负责设置时间戳—数据库,ORM框架(Hibernate)或应用程序员?

  • 您将使用哪些注释进行映射(例如@Temporal)?

我不只是在寻找一个可行的解决方案,而是一个安全且设计良好的解决方案。


如果您使用的是JPA注释,则可以使用@PrePersist@PreUpdate事件挂钩执行此操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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

1
2
3
4
5
6
7
8
9
@CreationTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name ="create_date")
private Date createDate;

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


利用这篇文章中的资源以及从不同来源左右传递的信息,我带来了这个优雅的解决方案,创建了以下抽象类

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
public interface TimeStamped {
    public Date getCreatedDate();
    public void setCreatedDate(Date createdDate);
    public Date getLastUpdated();
    public void setLastUpdated(Date lastUpdatedDate);
}

定义拦截器

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问题,时间戳算法快速而简单(主要是运算符<和运算符+,假设计算中没有涉及天/月/年)。而且,最重要的是,原始longjava.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;
}

只是强调:java.util.Calender不适用于时间戳。 java.util.Date是片刻,与时区等区域性事物无关。大多数数据库以这种方式存储东西(即使它们看起来不是这样;这通常是客户端软件中的时区设置;数据很好)


一个好方法是为所有实体建立一个公共基类。在此基类中,如果通常在所有实体(通用设计),创建和上次更新日期属性中命名,则可以拥有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
    );

这对我们有用。