2008-10-21 184 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 
    ); 

這對我們有效。