2008-10-21 112 views
186

对于某个Hibernate实体,我们需要存储其创建时间和上次更新的时间。你会如何设计?使用Hibernate和MySQL创建时间戳和上次更新时间戳

  • 你会在数据库中使用哪些数据类型(假设MySQL可能与JVM在不同的时区)?数据类型是否可以识别时区?

  • 你会在Java中使用哪些数据类型(DateCalendar,long,...)?

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

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

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

回答

2

一个好方法是为所有的实体设置一个通用的基类。在这个基类中,如果你的id属性在你的所有实体(一个通用设计)中共同命名,你的创建和最后一次更新日期属性,你可以拥有你的id属性。

对于创建日期,您只需保留一个java.util.Date属性。请确保始终使用新日期()进行初始化。

对于上次更新字段,您可以使用Timestamp属性,您需要使用@Version将其映射。通过这个Annotation,这个属性将会被Hibernate自动更新。请注意,Hibernate也会应用乐观锁定(这是一件好事)。

+1

使用时间戳列进行乐观锁定是个坏主意。始终使用整数版本列。原因在于,2个JVM可能处于不同的时间,并且可能不具有毫秒级的准确性。如果您改为使用数据库时间戳进行休眠,那将意味着来自数据库的额外选择。相反,只需使用版本号。 – sethu 2013-02-18 04:46:26

1

作为JAVA中的数据类型,我强烈建议使用java.util.Date。使用日历时,我遇到了非常讨厌的时区问题。看到这个Thread

对于设置我会建议您使用AOP方法或者你可以简单地使用触发器表上的时间戳(其实这是我曾经发现使用触发器可以接受的唯一的事情)。

2

只是为了加强:java.util.Calender不适用于时间戳java.util.Date是一段时间,不区分时区等区域性事物。以这种方式多数数据库存储的东西(即使他们似乎不,这通常是在客户端软件中的时区设置,数据良好)

224

如果您使用的是JPA注解,你可以使用@PrePersist和​​事件挂钩做到这一点:

@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注释并把事件代码在外部类。

+7

J2SE中没有任何问题,因为@PrePersist和@PerUpdate是JPA注释。 – Kdeveloper 2010-12-16 19:52:18

+10

使用Hibernate Session的时候很好,但不会工作。 – 2013-09-15 14:11:32

+2

@Kumar - 如果您使用纯Hibernate会话(而不是JPA),您可以尝试使用hibernate事件侦听器,尽管这不像JPA注释那么优雅和紧凑。 – Shailendra 2013-10-09 10:16:12

1

你可能会考虑将时间作为一个DateTime,和UTC。我通常使用DateTime而不是Timestamp,因为MySql在存储和检索数据时将日期转换为UTC并返回到本地时间。我宁愿将任何一种逻辑放在一个地方(业务层)。我敢肯定还有其他的情况,尽管使用Timestamp更可取。

12

谢谢大家谁帮助。做一些研究我自己(我是谁问的问题的家伙)后,这里是我发现意义最:

  • 数据库列类型:自1970年以来表现为毫秒的时区无关的数decimal(20)因为2^64有20个数字,磁盘空间便宜;让我们直截了当。另外,我也不会使用DEFAULT CURRENT_TIMESTAMP,也不会触发。我不想要DB中的魔法。

  • Java字段类型:long。 Unix时间戳很好地支持各种库,long没有Y2038问题,时间戳算法是快速和容易的(主要是运算符<和运算符+,假设计算中不涉及天/月/年)。并且,最重要的是,原始的longs和java.lang.Longs都是不可变的 —有效地通过值—而不是java.util.Dates;在调试其他人的代码时,我会非常生气,找到类似foo.getLastUpdate().setTime(System.currentTimeMillis())的东西。

  • ORM框架应该负责自动填写数据。

  • 我还没有测试过,但只看文档我认为@Temporal将完成这项工作;不确定我是否可以使用@Version达到此目的。 @PrePersist​​是手动控制的好替代品。将所有实体添加到图层超类型(通用基类),这是一个可爱的想法,前提是您确实需要为实体的全部加上时间戳。

+0

虽然多头和多头可能是不变的,但在你描述的情况下,这并不会帮助你。他们仍然可以说foo.setLastUpdate(new Long(System。的currentTimeMillis()); – 2008-10-23 14:52:35

88

以资源在这篇文章中的信息一起进行左右从不同的来源,我想出了这个优雅的解决方案,创建以下抽象类

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 { 
... 
} 
16

您也可以使用拦截器来设置值

创建一个名为时间戳的接口,你的实体imple换货

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

并与会话工厂

8

随着Olivier的解决方案进行注册,在更新语句可能会碰到:

com.mysql .jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:'created'列不能为空

为了解决这个问题,添加更新= false来“创造”属性的@Column批注:

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

如果您正在使用的会话API的PrePersist和更新前的回调,根据将无法正常工作到这个answer

我在我的代码中使用Hibernate Session的persist()方法,所以我可以使这项工作的唯一方法是使用下面的代码并在此blog post(也发布在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; 
    } 
} 
71

你可以只使用@CreationTimestamp@UpdateTimestamp

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

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

我们也有类似的情况。我们正在使用Mysql 5.7。

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

这对我们有效。