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.
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.
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;
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 {
...
}
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
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;
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 long
s 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 .
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;
}
}
Maintenant, il y a aussi les annotations @CreatedDate et @LastModifiedDate.
(Cadre de printemps)
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)
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;
}
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).
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.
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).
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.