282 votes

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

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

  • Quels types de données utiliseriez-vous dans la base de données (en supposant MySQL, éventuellement dans un fuseau horaire différent de celui de la JVM) ? Les types de données tiendront-ils compte du fuseau horaire ?

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

  • Qui serait responsable de la définition des horodatages - la base de données, le cadre ORM (Hibernate) ou le programmeur de l'application ?

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

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

0 votes

Je pense qu'il est préférable d'utiliser LocalDateTime au lieu de Date périmée dans les entités Java. De plus, la base de données ne devrait pas être consciente du fuseau horaire, ce qui cause de nombreux problèmes lors de la migration des données. J'utiliserais donc le type SQL datetime.

287voto

Guðmundur Bjarni Points 1778

Si vous utilisez les annotations JPA, vous pouvez utiliser @PrePersist y @PreUpdate Les crochets d'événements font cela :

@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 le @EntityListener sur la classe et placez le code de l'événement dans une classe externe.

7 votes

Fonctionne sans aucun problème en J2SE, car @PrePersist et @PerUpdate sont des annotations JPA.

2 votes

@Kumar - Si vous utilisez une simple session Hibernate (au lieu de JPA), vous pouvez essayer les écouteurs d'événements Hibernate, bien que ce ne soit pas très élégant et compact par rapport aux annotations JPA.

50 votes

Dans l'actuel Hibernate avec JPA, on peut utiliser "@CreationTimestamp" et "@UpdateTimestamp".

120voto

Olivier Refalo Points 12606

En utilisant les ressources de cet article ainsi que des informations prises à droite et à gauche de différentes sources, j'ai trouvé 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 que toutes vos entités l'étendent, par exemple :

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

7 votes

C'est bon jusqu'à ce que vous vouliez ajouter différents La seule façon d'obtenir le même effet sans classe de base est d'utiliser aspectj itd ou des écouteurs d'événements (voir la réponse de @kieren dixon).

3 votes

J'utiliserais pour cela un déclencheur MySQL, de sorte que même si l'entité complète n'est pas enregistrée ou est modifiée par une application externe ou une requête manuelle, ces champs seront mis à jour.

3 votes

Pouvez-vous me donner un exemple de travail car je rencontre une exception. not-null property references a null or transient value: package.path.ClassName.created

18voto

Kieren Dixon Points 597

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

Créez une interface appelée TimeStamped que vos entités mettent en œuvre.

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 l'enregistrer auprès de la fabrique de sessions

1 votes

Ça marche, merci. Informations complémentaires docs.jboss.org/hibernate/core/4.0/manual/en-US/html_single/

0 votes

C'est une solution, si vous travaillez avec SessionFactory au lieu d'EntityManager !

0 votes

Juste pour ceux qui souffrent d'un problème similaire au mien dans ce contexte : si votre entité ne définit pas elle-même ces champs supplémentaires (createdAt, ...) mais les hérite d'une classe parente, alors cette classe parente doit être annotée avec @MappedSuperclass - sinon Hibernate ne trouve pas ces champs.

12voto

ngn Points 2820

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

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

  • Type de champ Java : long . Le timestamp Unix est bien supporté par diverses librairies, long n'a pas de problème Y2038, l'arithmétique de l'horodatage est rapide et facile (principalement l'opérateur < et opérateur + (en supposant qu'aucun jour/mois/année n'intervient dans les calculs). Et, plus important encore, les deux primitives long et java.lang.Long sont immuable -Effectivement passé en valeur- contrairement à java.util.Date je serais vraiment énervé de trouver quelque chose comme foo.getLastUpdate().setTime(System.currentTimeMillis()) quand vous déboguez le code de quelqu'un d'autre.

  • Le cadre ORM devrait se charger de remplir les données automatiquement.

  • Je n'ai pas encore testé cela, mais en regardant la documentation, je suppose que @Temporal fera l'affaire ; je ne suis pas certain de pouvoir utiliser @Version à cette fin. @PrePersist y @PreUpdate sont de bonnes alternatives pour contrôler cela manuellement. Ajouter cela au supertype de la couche (classe de base commune) pour toutes les entités est une bonne idée, à condition que l'on veuille vraiment que l'horodatage soit utilisé pour les entités suivantes todos de vos entités.

0 votes

Même si les longs et les longs sont immuables, cela ne vous aidera pas dans la situation que vous décrivez. On peut toujours dire que foo.setLastUpdate(new Long(System.currentTimeMillis()) ;

2 votes

C'est bien. Hibernate a besoin du setter de toute façon (sinon il essaiera d'accéder au champ directement par réflexion). Je parlais de la difficulté à trouver qui modifie l'horodatage à partir du code de notre application. C'est délicat lorsque vous pouvez le faire en utilisant un getter.

0 votes

Je suis d'accord avec votre affirmation selon laquelle le framework ORM devrait être responsable du remplissage automatique de la date, mais j'irais un peu plus loin en disant que la date devrait être définie à partir de l'horloge du serveur de base de données, plutôt que du client. Je ne suis pas sûr que cela permette d'atteindre cet objectif. En sql, je peux le faire en utilisant la fonction sysdate, mais je ne sais pas comment le faire dans Hibernate ou toute autre implémentation JPA.

2voto

bernardn Points 897

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 nommée communément dans toutes vos entités (une conception commune), vos propriétés de date de création et de dernière mise à jour.

Pour la date de création, il suffit de conserver une java.util.Date propriété. Assurez-vous de toujours l'initialiser avec nouvelle date() .

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

2 votes

L'utilisation d'une colonne d'horodatage pour le verrouillage optimiste est une mauvaise idée. Utilisez toujours une colonne de version entière. La raison en est que deux JVM peuvent être à des heures différentes et ne pas avoir une précision de l'ordre de la milliseconde. Si vous demandez à Hibernate d'utiliser l'horodatage de la base de données, cela entraînera des sélections supplémentaires dans la base de données. Il suffit donc d'utiliser le numéro de version.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X