web-dev-qa-db-fra.com

Horodatage de création et horodatage de la dernière mise à jour avec Hibernate et MySQL

Pour une certaine entité Hibernate, nous devons stocker son heure de création et la dernière fois où elle a été mise à jour. Comment concevez-vous cela?

  • Quels types de données utiliseriez-vous dans la base de données (en supposant que MySQL, éventuellement dans un fuseau horaire différent de celui de la machine virtuelle Java)? Les types de données seront-ils sensibles au fuseau horaire?

  • Quels types de données utiliseriez-vous dans Java (Date, Calendar, long, ...)?

  • À qui confieriez-vous la configuration des horodatages: la base de données, le framework ORM (Hibernate) ou le programmeur d'application?

  • Quelles annotations utiliseriez-vous pour le mappage (par exemple @Temporal)?

Je ne cherche pas seulement une solution qui fonctionne, mais une solution sûre et bien conçue.

222
ngn

Si vous utilisez les annotations JPA, vous pouvez utiliser les hooks d'événement @PrePersist et @PreUpdate pour procéder comme suit:

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

ou vous pouvez utiliser l'annotation @EntityListener sur la classe et placer le code d'événement dans une classe externe.

251
Guðmundur Bjarni

Vous pouvez simplement utiliser @CreationTimestamp et @UpdateTimestamp:

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

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

Prenant les ressources de ce post avec des informations prises de gauche à droite, différentes sources, je suis venu avec cette solution élégante, créer la classe abstraite suivante

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

et demandez à toutes vos entités de l'étendre, par exemple:

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

Vous pouvez également utiliser un intercepteur pour définir les valeurs

Créez une interface appelée TimeStamped que vos entités implémentent

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

Définir l'intercepteur

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

Et enregistrez-le auprès de la session factory

17
Kieren Dixon

Avec la solution d'Olivier, lors des instructions de mise à jour, vous pouvez rencontrer:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: la colonne 'créée' ne peut être nulle

Pour résoudre ce problème, ajoutez updatable = false à l'annotation @Column de l'attribut "created":

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

Merci à tous ceux qui ont aidé. Après avoir fait quelques recherches moi-même (c'est moi qui ai posé la question), voici ce que j'ai trouvé le plus logique:

  • Type de colonne de la base de données: le nombre de millisecondes dépendant du fuseau horaire depuis 1970, représenté par decimal(20) car 2 ^ 64 comporte 20 chiffres et que l'espace disque est bon marché; soyons simples. De plus, je n’utiliserai ni _DEFAULT CURRENT_TIMESTAMP_, ni déclencheurs. Je ne veux pas de magie dans la base de données.

  • Type de champ Java: long. L'horodatage Unix est bien pris en charge dans différentes bibliothèques, long n'a pas de problèmes Y2038, l'arithmétique d'horodatage est rapide et facile (principalement l'opérateur _<_ et l'opérateur _+_, en supposant qu'aucun jour/mois/année ne soit impliqué dans les calculs. ). Et, plus important encore, les deux primitives longs et _Java.lang.Long_ s sont immuables - effectivement passées par valeur - contrairement à _Java.util.Date_ s; Je serais vraiment énervé de trouver quelque chose comme foo.getLastUpdate().setTime(System.currentTimeMillis()) lors du débogage du code de quelqu'un d'autre.

  • Le cadre ORM devrait être responsable de la saisie automatique des données.

  • Je n'ai pas encore testé cela, mais en regardant la documentation, je suppose que @Temporal fera le travail; Je ne sais pas si je pourrais utiliser _@Version_ à cette fin. @PrePersist et @PreUpdate sont de bonnes alternatives pour contrôler cela manuellement. Ajouter cela au sur-type de couche (classe de base commune) pour toutes les entités est une idée intéressante, à condition que vous souhaitiez vraiment un horodatage pour toutes les entités .

12
ngn

Si vous utilisez l'API de session, les rappels PrePersist et PreUpdate ne fonctionneront pas conformément à ceci réponse .

J'utilise la méthode persist () de Hibernate Session dans mon code afin que je puisse faire ce travail était avec le code ci-dessous et après ceci blog post (également publié dans le 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;
    }
}
6
vicch
3
Oreste Viron

Juste pour renforcer: Java.util.Calender n'est pas pour les horodatages. Java.util.Date est pour un instant agnostique de choses régionales comme le fuseau horaire. La plupart des bases de données stockent les éléments de cette manière (même s’ils ne le semblent pas; il s’agit généralement d’un paramètre de fuseau horaire dans le logiciel client; les données sont correctes)

2
davetron5000

Le code suivant a fonctionné pour moi.

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

Une bonne approche consiste à avoir une classe de base commune pour toutes vos entités. Dans cette classe de base, vous pouvez avoir votre propriété id si elle est communément nommée dans toutes vos entités (conception commune), vos propriétés de création et de date de dernière mise à jour.

Pour la date de création, vous conservez simplement une propriété Java.util.Date. Assurez-vous de toujours l'initialiser avec new Date ().

Pour le dernier champ de mise à jour, vous pouvez utiliser une propriété Timestamp, vous devez la mapper avec @Version. Avec cette annotation, la propriété sera automatiquement mise à jour par Hibernate. Attention, Hibernate appliquera également un verrouillage optimiste (c'est une bonne chose).

2
bernardn

Vous pourriez envisager de stocker l'heure en tant que DateTime et en UTC. J'utilise généralement DateTime au lieu de Timestamp en raison du fait que MySql convertit les dates en UTC et en heure locale lors du stockage et de la récupération des données. Je préférerais conserver une telle logique au même endroit (couche métier). Je suis sûr qu'il existe d'autres situations où l'utilisation de l'horodatage est préférable.

1
mmacaulay

En tant que type de données dans Java, il est fortement recommandé d'utiliser Java.util.Date. J'ai rencontré des problèmes de fuseau horaire assez désagréables lors de l'utilisation de Calendar. Voir ceci Fil .

Pour régler les horodatages, je vous recommande d’utiliser soit une approche AOP, soit d’utiliser simplement les déclencheurs sur la table (c’est en fait la seule chose pour laquelle j’ai trouvé l’utilisation de déclencheurs acceptable).

1
huo73

Nous avons eu une situation similaire. Nous utilisions Mysql 5.7.

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

Cela a fonctionné pour nous.

0
amdg