Für eine bestimmte Hibernate-Entität müssen die Erstellungszeit und die letzte Aktualisierung gespeichert werden. Wie würden Sie das gestalten?
Welche Datentypen würden Sie in der Datenbank verwenden (unter der Annahme, dass sich MySQL möglicherweise in einer anderen Zeitzone als die JVM befindet)? Werden die Datentypen zeitzonenabhängig sein?
Welche Datentypen würden Sie in Java (Date
, Calendar
, long
, ...) verwenden?
Wen würden Sie für das Festlegen der Zeitstempel verantwortlich machen - die Datenbank, das ORM-Framework (Ruhezustand) oder den Anwendungsprogrammierer?
Welche Anmerkungen würden Sie für die Zuordnung verwenden (z. B. @Temporal
)?
Ich bin nicht nur auf der Suche nach einer funktionierenden Lösung, sondern auch nach einer sicheren und durchdachten Lösung.
Wenn Sie die JPA-Anmerkungen verwenden, können Sie @PrePersist
und @PreUpdate
Event Hooks machen das:
@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();
}
}
oder du kannst das @EntityListener
Anmerkung zur Klasse und Platzieren des Ereigniscodes in einer externen Klasse.
Sie können einfach @CreationTimestamp
und @UpdateTimestamp
:
@CreationTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "create_date")
private Date createDate;
@UpdateTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "modify_date")
private Date modifyDate;
Indem ich die Ressourcen in diesem Beitrag zusammen mit Informationen aus verschiedenen Quellen nach links und rechts genommen habe, habe ich mit dieser eleganten Lösung die folgende abstrakte Klasse erstellt
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();
}
}
und lassen Sie alle Ihre Entitäten erweitern, zum Beispiel:
@Entity
@Table(name = "campaign")
public class Campaign extends AbstractTimestampEntity implements Serializable {
...
}
Sie können auch einen Interceptor verwenden, um die Werte festzulegen
Erstellen Sie eine Schnittstelle mit dem Namen TimeStamped, die Ihre Entitäten implementieren
public interface TimeStamped {
public Date getCreatedDate();
public void setCreatedDate(Date createdDate);
public Date getLastUpdated();
public void setLastUpdated(Date lastUpdatedDate);
}
Definieren Sie den Abfangjäger
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;
}
}
Und registrieren Sie es bei der Session Factory
Mit Oliviers Lösung können Sie bei Update-Anweisungen auf Folgendes stoßen:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Die Spalte 'created' darf nicht null sein
Um dies zu lösen, fügen Sie updatable = false zur @ Column-Annotation des Attributs "created" hinzu:
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created", nullable = false, updatable=false)
private Date created;
Vielen Dank an alle, die mitgeholfen haben. Nachdem ich einige Nachforschungen angestellt habe (ich bin derjenige, der die Frage gestellt hat), habe ich Folgendes für sinnvoll befunden:
Typ der Datenbankspalte: Die zeitzonenunabhängige Anzahl von Millisekunden seit 1970, dargestellt als decimal(20)
, da 2 ^ 64 20 Stellen hat und der Speicherplatz günstig ist. Lass uns einfach sein. Außerdem werde ich weder DEFAULT CURRENT_TIMESTAMP
Noch Trigger verwenden. Ich will keine Magie in der DB.
Java-Feldtyp: long
. Der Unix-Zeitstempel wird in verschiedenen Bibliotheken gut unterstützt, long
hat keine Probleme mit Y2038, die Zeitstempel-Arithmetik ist schnell und einfach (hauptsächlich Operator <
Und Operator +
, Vorausgesetzt keine Tage/Monate)/Jahre sind an den Berechnungen beteiligt). Und am wichtigsten ist, dass beide primitiven long
s und Java.lang.Long
nveränderlich - im Gegensatz zu Java.util.Date
- effektiv als Wert übergeben werden. Ich wäre wirklich sauer, wenn ich etwas wie foo.getLastUpdate().setTime(System.currentTimeMillis())
finden würde, wenn ich den Code eines anderen debuggen würde.
Das ORM-Framework sollte für das automatische Ausfüllen der Daten verantwortlich sein.
Ich habe das noch nicht getestet, aber ich gehe nur davon aus, dass @Temporal
den Job erledigt; Ich bin mir nicht sicher, ob ich @Version
für diesen Zweck verwenden könnte. @PrePersist
und @PreUpdate
sind gute Alternativen, um dies manuell zu steuern. Dies dem Layer-Supertyp (gemeinsame Basisklasse) für alle Entitäten hinzuzufügen, ist eine nette Idee, vorausgesetzt, Sie möchten wirklich eine Zeitstempelung für alle Ihrer Entitäten.
Wenn Sie die Sitzungs-API verwenden, funktionieren die PrePersist- und PreUpdate-Rückrufe nicht entsprechend answer .
Ich verwende die persist () -Methode von Hibernate Session in meinem Code, so dass ich diese Arbeit nur mit dem folgenden Code ausführen konnte Blog-Beitrag (auch in Antwort =).
@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;
}
}
Jetzt gibt es auch die Annotationen @CreatedDate und @LastModifiedDate.
(Frühlingsrahmen)
Nur zur Verstärkung: Java.util.Calender
ist nicht für Zeitstempel. Java.util.Date
ist für einen Moment agnostisch gegenüber regionalen Dingen wie Zeitzonen. Die meisten Datenbankspeicherfunktionen werden auf diese Weise ausgeführt (auch wenn dies nicht der Fall zu sein scheint; dies ist normalerweise eine Zeitzoneneinstellung in der Client-Software; die Daten sind in Ordnung).
Der folgende Code hat bei mir funktioniert.
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;
}
Ein guter Ansatz ist es, eine gemeinsame Basisklasse für alle Ihre Entitäten zu haben. In dieser Basisklasse können Sie Ihre ID-Eigenschaft haben, wenn sie in all Ihren Entitäten (einem gemeinsamen Entwurf), Ihren Eigenschaften für das Erstellungsdatum und das Datum der letzten Aktualisierung gemeinsam benannt ist.
Für das Erstellungsdatum behalten Sie einfach die Eigenschaft Java.util.Date. Stellen Sie sicher, dass Sie es immer mit new Date () initialisieren.
Für das letzte Aktualisierungsfeld können Sie eine Timestamp-Eigenschaft verwenden, die Sie mit @Version zuordnen müssen. Mit dieser Annotation wird die Eigenschaft von Hibernate automatisch aktualisiert. Beachten Sie, dass Hibernate auch optimistische Sperren anwendet (das ist eine gute Sache).
Sie können die Uhrzeit als DateTime und in UTC speichern. Ich verwende in der Regel DateTime anstelle von Timestamp, da MySql beim Speichern und Abrufen der Daten Datumsangaben in UTC und zurück in die Ortszeit umwandelt. Ich möchte diese Art von Logik lieber an einem Ort aufbewahren (Business-Ebene). Ich bin mir sicher, dass es andere Situationen gibt, in denen die Verwendung von Timestamp vorzuziehen ist.
Als Datentyp Java Ich empfehle dringend, Java.util.Date zu verwenden. Bei der Verwendung des Kalenders sind ziemlich unangenehme Zeitzonenprobleme aufgetreten. Siehe hierzu Thread .
Zum Setzen der Zeitstempel würde ich entweder einen AOP-Ansatz empfehlen oder Sie könnten einfach Trigger für die Tabelle verwenden (eigentlich ist dies das einzige, was ich jemals für akzeptabel halte, Trigger zu verwenden).
Wir hatten eine ähnliche Situation. Wir haben Mysql 5.7 verwendet.
CREATE TABLE my_table (
...
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
Das hat bei uns funktioniert.