Timestamp di creazione e timestamp dell’ultimo aggiornamento con Hibernate e MySQL

Per una determinata entity framework di Hibernate abbiamo l’obbligo di memorizzare il suo tempo di creazione e l’ultima volta che è stato aggiornato. Come lo progetteresti?

Non cerco solo una soluzione funzionante, ma una soluzione sicura e ben progettata.

Se stai usando le annotazioni JPA, puoi usare @PrePersist e @PreUpdate hook degli eventi:

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

oppure puoi usare l’annotazione @EntityListener sulla class e inserire il codice evento in una class esterna.

Prendendo le risorse in questo post insieme alle informazioni prese a destra e sinistra da fonti diverse, sono arrivato con questa soluzione elegante, creare la seguente class astratta

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

e fare in modo che tutte le quadro lo estendano, ad esempio:

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

Puoi semplicemente usare @CreationTimestamp e @UpdateTimestamp :

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

Puoi anche usare un intercettore per impostare i valori

Crea un’interfaccia chiamata TimeStamped che le tue entity framework implementano

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

Definire l’intercettore

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

E registralo con la fabbrica della sessione

Grazie a tutti coloro che hanno aiutato. Dopo aver fatto qualche ricerca (sono il ragazzo che ha posto la domanda), ecco cosa ho trovato più sensato:

  • Tipo di colonna del database: il numero di millisecondi del fuso orario dal 1970 rappresentato come decimal(20) perché 2 ^ 64 ha 20 cifre e lo spazio su disco è economico; siamo semplici. Inoltre, non userò né DEFAULT CURRENT_TIMESTAMP , né i trigger. Non voglio magia nel DB.

  • Tipo di campo Java: long . Il timestamp Unix è ben supportato su varie librerie, a long non ha problemi Y2038, l’aritmetica timestamp è veloce e facile (principalmente operatore < e operatore + , supponendo che non siano coinvolti giorni / mesi / anni nei calcoli). E, cosa più importante, sia i primitivi long che java.lang.Long sono immutabili, passati in modo efficace per valore, a differenza di java.util.Date s; Sarei davvero incazzato per trovare qualcosa come foo.getLastUpdate().setTime(System.currentTimeMillis()) durante il debug del codice di qualcun altro.

  • La struttura ORM dovrebbe essere responsabile della compilazione automatica dei dati.

  • Non ho ancora provato questo, ma solo guardando i documenti presumo che @Temporal farà il lavoro; Non sono sicuro se potrei usare @Version per questo scopo. @PrePersist e @PreUpdate sono buone alternative per controllarlo manualmente. L'aggiunta di questo al supertipo di livello (class di base comune) per tutte le entity framework, è un'idea carina a patto che tu voglia veramente il timestamping per tutte le tue entity framework.

Con la soluzione di Olivier, durante le dichiarazioni di aggiornamento si può incorrere in:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: la colonna ‘created’ non può essere nullo

Per risolvere questo problema, aggiungi updatable = false all’annotazione @Column dell’attributo “created”:

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

Se si utilizza l’API Session, i callback PrePersist e PreUpdate non funzioneranno in base a questa risposta .

Sto usando il metodo persist () di Hibernate Session nel mio codice, quindi l’unico modo per farlo funzionare era con il codice seguente e seguendo questo post sul blog (anch’esso pubblicato nella risposta ).

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

Un buon approccio è avere una class base comune per tutte le tue entity framework. In questa class base, puoi avere la tua proprietà id se è comunemente denominata in tutte le tue quadro (un design comune), la tua creazione e le ultime proprietà della data di aggiornamento.

Per la data di creazione, è sufficiente mantenere una proprietà java.util.Date . Assicurati di inizializzarlo sempre con la nuova data () .

Per l’ultimo campo di aggiornamento, puoi utilizzare una proprietà Timestamp, devi mapparla con @Version. Con questa annotazione la proprietà verrà automaticamente aggiornata da Hibernate. Attenzione che Hibernate applicherà anche il blocco ottimistico (è una buona cosa).

Solo per rinforzare: java.util.Calender non è per i timestamp . java.util.Date è per un momento nel tempo, agnostico delle cose regionali come i fusi orari. La maggior parte dei database archivia le cose in questo modo (anche se sembrano non farlo, di solito è un’impostazione del fuso orario nel software client, i dati sono buoni)

Come tipo di dati in JAVA, consiglio vivamente di utilizzare java.util.Date. Ho avuto problemi di fuso orario piuttosto brutti quando uso Calendar. Vedi questa discussione .

Per impostare i timestamp suggerirei di utilizzare un approccio AOP o semplicemente utilizzare i trigger sul tavolo (in realtà questa è l’unica cosa che ritengo sia accettabile l’uso di trigger).

Potresti considerare di memorizzare l’ora come DateTime e in UTC. Generalmente utilizzo DateTime invece di Timestamp perché MySql converte le date in UTC e di nuovo in ora locale durante l’archiviazione e il recupero dei dati. Preferisco mantenere qualsiasi tipo di logica in un unico posto (livello aziendale). Sono sicuro che ci sono altre situazioni in cui l’uso del Timestamp è preferibile.

Abbiamo avuto una situazione simile. Stavamo usando Mysql 5.7.

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

Questo ha funzionato per noi.