Java - JPA - @ Versions Annotation

118

Wie funktioniert @VersionAnnotation in JPA?

Ich habe verschiedene Antworten gefunden, deren Auszug wie folgt lautet:

JPA verwendet ein Versionsfeld in Ihren Entitäten, um gleichzeitige Änderungen am gleichen Datenspeicherdatensatz zu erkennen. Wenn die JPA-Laufzeit einen Versuch erkennt, denselben Datensatz gleichzeitig zu ändern, wird eine Ausnahme für die Transaktion ausgelöst, die versucht, zuletzt einen Commit durchzuführen.

Aber ich bin mir immer noch nicht sicher, wie es funktioniert.


Auch ab folgenden Zeilen:

Sie sollten Versionsfelder als unveränderlich betrachten. Das Ändern des Feldwerts hat undefinierte Ergebnisse.

Bedeutet das, dass wir unser Versionsfeld als deklarieren sollten final?

Yatendra Goel
quelle
1
Es wird lediglich die Version in jeder Aktualisierungsabfrage überprüft / aktualisiert : UPDATE myentity SET mycolumn = 'new value', version = version + 1 WHERE version = [old.version]. Wenn jemand den Datensatz aktualisiert hat, old.versionstimmt er nicht mehr mit dem in der Datenbank überein, und die where-Klausel verhindert, dass das Update durchgeführt wird. Die 'aktualisierten Zeilen' werden von 0JPA erkannt, um zu schließen, dass eine gleichzeitige Änderung stattgefunden hat.
Stijn de Witt

Antworten:

189

Aber ich bin mir immer noch nicht sicher, wie es funktioniert?

Angenommen, eine Entität MyEntityverfügt über eine mit Anmerkungen versehene versionEigenschaft:

@Entity
public class MyEntity implements Serializable {    

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Version
    private Long version;

    //...
}

Bei der Aktualisierung wird das mit Anmerkungen versehene Feld @Versioninkrementiert und der WHEREKlausel hinzugefügt.

UPDATE MYENTITY SET ..., VERSION = VERSION + 1 WHERE ((ID = ?) AND (VERSION = ?))

Wenn die WHEREKlausel nicht mit einem Datensatz übereinstimmt (da dieselbe Entität bereits von einem anderen Thread aktualisiert wurde), gibt der Persistenzanbieter einen aus OptimisticLockException.

Bedeutet das, dass wir unser Versionsfeld als endgültig deklarieren sollten?

Nein, aber Sie könnten in Betracht ziehen, den Setter zu schützen, da Sie ihn nicht nennen sollten.

Pascal Thivent
quelle
5
Ich habe mit diesem Code eine Nullzeiger-Ausnahme erhalten, da Long-Initialisierungen als Null wahrscheinlich explizit mit 0L initialisiert werden sollten.
Markus
2
Verlassen Sie sich nicht darauf, es automatisch zu entpacken longoder es einfach aufzurufen longValue(). Sie benötigen explizite nullÜberprüfungen. Eine explizite Initialisierung würde ich nicht tun, da dieses Feld vom ORM-Anbieter verwaltet werden soll. Ich denke, es wird 0Lbeim ersten Einfügen in die Datenbank auf gesetzt. Wenn es also auf nulldiesen Wert gesetzt ist, wird Ihnen mitgeteilt, dass dieser Datensatz noch nicht beibehalten wurde.
Stijn de Witt
Wird dies verhindern, dass eine Entität mehr als einmal erstellt wird (versucht wird)? Ich meine, eine JMS-Themennachricht löst die Erstellung einer Entität aus und es gibt mehrere Instanzen einer Anwendung, die die Nachricht abhört. Ich möchte nur den eindeutigen Fehler bei der Verletzung von Einschränkungen vermeiden.
Manu
Entschuldigung, mir ist klar, dass dies ein alter Thread ist, aber berücksichtigt @Version auch den verschmutzten Cache in Clusterumgebungen?
Shawn Eion Smith
3
Wenn Sie Spring Data JPA verwenden, ist es möglicherweise besser, einen Wrapper Longals ein Grundelement longfür das @VersionFeld zu verwenden, da SimpleJpaRepository.save(entity)ein Feld mit der Nullversion wahrscheinlich als Indikator dafür behandelt wird, dass die Entität neu ist. Wenn die Entität neu ist (wie durch die Version null angegeben), ruft Spring auf em.persist(entity). Wenn die Version jedoch einen Wert hat, wird sie aufgerufen em.merge(entity). Aus diesem Grund sollte ein Versionsfeld, das als Wrapper-Typ deklariert ist, nicht initialisiert werden.
Rdguam
33

Obwohl die Antwort von @Pascal vollkommen gültig ist, finde ich aus meiner Erfahrung den folgenden Code hilfreich, um ein optimistisches Sperren zu erreichen:

@Entity
public class MyEntity implements Serializable {    
    // ...

    @Version
    @Column(name = "optlock", columnDefinition = "integer DEFAULT 0", nullable = false)
    private long version = 0L;

    // ...
}

Warum? Weil:

  1. Optimistisches Sperren funktioniert nicht wenn das mit Anmerkungen versehene Feld versehentlich auf gesetzt @Versionist null.
  2. Da dieses spezielle Feld nicht unbedingt eine Geschäftsversion des Objekts ist, bevorzuge ich es, ein solches Feld so zu benennen, optlockanstatt irreführend zu seinversion .

Erster Punkt spielt keine Rolle , wenn Anwendung verwendet nur JPA für Daten in die Datenbank einfügen, als PPV - Anbieter erzwingen wird 0für @versionFeld zum Zeitpunkt der Erstellung. Es werden jedoch fast immer auch einfache SQL-Anweisungen verwendet (zumindest während Unit- und Integrationstests).

G. Demecki
quelle
Ich nenne es u_lmod(zuletzt geänderter Benutzer), wobei der Benutzer ein menschlicher oder definierter (automatisierter) Prozess / eine Entität / eine App sein kann (daher u_lmod_idkönnte das Speichern zusätzlich auch sinnvoll sein). (Es ist meiner Ansicht nach ein sehr grundlegendes Business-Meta-Attribut). Es speichert normalerweise seinen Wert als DATE (TIME) (dh Infos über Jahr ... Millis) in UTC (wenn es wichtig ist, die Zeitzone "Position" des Editors zu speichern, dann mit Zeitzone). Darüber hinaus sehr nützlich für DWH-Synchronisierungsszenarien.
Andreas Dietrich
7
Ich denke, Bigint anstelle einer Ganzzahl sollte im DB-Typ verwendet werden, um einem langen Java-Typ zu entsprechen.
Dmitry
Guter Punkt, Dmitry, danke, obwohl es bei der Versionierung von IMHO nicht wirklich wichtig ist.
G. Demecki
9

Jedes Mal, wenn eine Entität in der Datenbank aktualisiert wird, wird das Versionsfeld um eins erhöht. Jede Operation, die die Entität in der Datenbank aktualisiert, wird an WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASEihre Abfrage angehängt .

Bei der Überprüfung der betroffenen Zeilen Ihres Vorgangs kann das jpa-Framework sicherstellen, dass zwischen dem Laden und dem Beibehalten Ihrer Entität keine gleichzeitigen Änderungen vorgenommen wurden, da die Abfrage Ihre Entität nicht in der Datenbank finden würde, wenn die Versionsnummer zwischen Laden und Beibehalten erhöht wurde.

stefanglase
quelle
Nicht über JPAUpdateQuery. "Jede Operation, die die Entität in der Datenbank aktualisiert, hat WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASE an ihre Abfrage angehängt" ist nicht wahr.
Pedro Borges
2

Version, die verwendet wird, um sicherzustellen, dass jeweils nur ein Update durchgeführt wird. Der JPA-Anbieter überprüft die Version. Wenn sich die erwartete Version bereits erhöht, aktualisiert bereits eine andere Person die Entität, sodass eine Ausnahme ausgelöst wird.

Das Aktualisieren des Entitätswerts wäre also sicherer und optimistischer.

Wenn sich der Wert häufig ändert, sollten Sie möglicherweise das Versionsfeld nicht verwenden. Zum Beispiel "eine Entität mit einem Zählerfeld, die sich bei jedem Zugriff auf eine Webseite erhöht"

uudashr
quelle
0

Fügen Sie einfach ein wenig mehr Informationen hinzu.

JPA verwaltet die Version unter der Haube für Sie, tut dies jedoch nicht, wenn Sie Ihren Datensatz über aktualisieren JPAUpdateClause. In solchen Fällen müssen Sie das Versionsinkrement manuell zur Abfrage hinzufügen.

Gleiches gilt für die Aktualisierung über JPQL, dh nicht für eine einfache Änderung der Entität, sondern für einen Aktualisierungsbefehl für die Datenbank, selbst wenn dies im Ruhezustand erfolgt

Pedro

Pedro Borges
quelle
3
Was ist JPAUpdateClause?
Karl Richter
Gute Frage, einfache Antwort ist: schlechtes Beispiel :) JPAUpdateClause ist QueryDSL-spezifisch. Denken Sie daran, ein Update mit JPQL auszuführen, anstatt einfach den Status einer Entität im Code zu beeinflussen.
Pedro Borges