Optimistisches Sperren in JPA

1. Einleitung

Wenn es um Unternehmensanwendungen geht, ist es wichtig, den gleichzeitigen Zugriff auf eine Datenbank ordnungsgemäß zu verwalten. Dies bedeutet, dass wir in der Lage sein sollten, mehrere Transaktionen effektiv und vor allem fehlerfrei abzuwickeln.

Darüber hinaus müssen wir sicherstellen, dass die Daten zwischen gleichzeitigen Lesevorgängen und Aktualisierungen konsistent bleiben.

Um dies zu erreichen, können wir den optimistischen Sperrmechanismus verwenden, der von der Java Persistence API bereitgestellt wird. Dies führt dazu, dass sich mehrere Aktualisierungen, die gleichzeitig an denselben Daten vorgenommen wurden, nicht gegenseitig stören.

2. Optimistisches Sperren verstehen

Um optimistisches Sperren verwenden zu können, benötigen wir eine Entität mit einer Eigenschaft mit @ Versions- Annotation . Während der Verwendung enthält jede Transaktion, die Daten liest, den Wert der version-Eigenschaft.

Bevor die Transaktion eine Aktualisierung vornehmen möchte, überprüft sie die Versionseigenschaft erneut.

Wenn sich der Wert in der Zwischenzeit geändert hat, wird eine OptimisticLockException ausgelöst. Andernfalls schreibt die Transaktion die Aktualisierung fest und erhöht eine Wertversionseigenschaft.

3. Pessimistisches Sperren vs. optimistisches Sperren

Es ist gut zu wissen, dass JPA im Gegensatz zu optimistischem Sperren pessimistisches Sperren ermöglicht. Dies ist ein weiterer Mechanismus für den gleichzeitigen Zugriff auf Daten.

Wir behandeln pessimistisches Sperren in einem unserer vorherigen Artikel - Pessimistisches Sperren in JPA. Lassen Sie uns herausfinden, was der Unterschied ist und wie wir von jeder Art von Verriegelung profitieren können.

Wie bereits erwähnt, basiert das optimistische Sperren darauf, Änderungen an Entitäten durch Überprüfen ihres Versionsattributs zu erkennen . Wenn eine gleichzeitige Aktualisierung stattfindet, tritt eine OptmisticLockException auf. Danach können wir versuchen, die Daten erneut zu aktualisieren.

Wir können uns vorstellen, dass dieser Mechanismus für Anwendungen geeignet ist, die viel mehr lesen als aktualisieren oder löschen. Darüber hinaus ist es in Situationen nützlich, in denen Entitäten für einige Zeit getrennt werden müssen und Sperren nicht gehalten werden können.

Im Gegensatz dazu umfasst der pessimistische Sperrmechanismus das Sperren von Entitäten auf Datenbankebene.

Jede Transaktion kann eine Sperre für Daten erhalten. Solange die Sperre besteht, kann keine Transaktion die gesperrten Daten lesen, löschen oder aktualisieren. Wir können davon ausgehen, dass die Verwendung von pessimistischem Sperren zu Deadlocks führen kann. Es gewährleistet jedoch eine größere Datenintegrität als ein optimistisches Sperren.

4. Versionsattribute

Versionsattribute sind Eigenschaften mit @ Versions- Annotation. Sie sind notwendig, um ein optimistisches Sperren zu ermöglichen. Sehen wir uns eine Beispielentitätsklasse an:

@Entity public class Student { @Id private Long id; private String name; private String lastName; @Version private Integer version; // getters and setters }

Es gibt verschiedene Regeln, die wir beim Deklarieren von Versionsattributen befolgen sollten:

  • Jede Entitätsklasse darf nur ein Versionsattribut haben
  • Es muss in der Primärtabelle für eine Entität platziert werden, die mehreren Tabellen zugeordnet ist
  • Der Typ eines Versionsattributs muss einer der folgenden sein: int , Integer , long , Long , short , Short , java.sql.Timestamp

Wir sollten wissen, dass wir einen Wert des Versionsattributs über eine Entität abrufen können, aber wir dürfen ihn nicht aktualisieren oder erhöhen. Dies kann nur der Persistenzanbieter, sodass die Daten konsistent bleiben.

Es ist zu beachten, dass Persistenzanbieter optimistisches Sperren für Entitäten unterstützen können, die keine Versionsattribute haben. Es ist jedoch eine gute Idee, bei der Arbeit mit optimistischem Sperren immer Versionsattribute einzubeziehen.

Wenn wir versuchen, eine Entität zu sperren, die ein solches Attribut nicht enthält und der Persistenzanbieter es nicht unterstützt, führt dies zu einer PersitenceException .

5. Sperrmodi

JPA bietet uns zwei verschiedene optimistische Sperrmodi (und zwei Aliase):

  • OPTIMISTISCH - Es wird eine optimistische Lesesperre für alle Entitäten erhalten, die ein Versionsattribut enthalten
  • OPTIMISTIC_FORCE_INCREMENT - Es erhält eine optimistische Sperre wie OPTIMISTIC und erhöht zusätzlich den Versionsattributwert
  • READ - es ist ein Synonym für OPTIMISTIC
  • SCHREIBEN - es ist ein Synonym für OPTIMISTIC_FORCE_INCREMENT

Wir können alle oben aufgeführten Typen in der LockModeType- Klasse finden.

5.1. OPTIMISTIC ( READ )

Wie wir bereits wissen, sind die Sperrmodi OPTIMISTIC und READ Synonyme. In der JPA-Spezifikation wird jedoch empfohlen, OPTIMISTIC in neuen Anwendungen zu verwenden.

Immer wenn wir den OPTIMISTIC-Sperrmodus anfordern , verhindert ein Persistenzanbieter, dass unsere Daten sowohl fehlerhafte als auch nicht wiederholbare Lesevorgänge ausführen .

Einfach ausgedrückt sollte es sicherstellen, dass bei keiner Transaktion Änderungen an Daten vorgenommen werden, die von einer anderen Transaktion stammen:

  • wurde aktualisiert oder gelöscht, aber nicht festgeschrieben
  • wurde in der Zwischenzeit erfolgreich aktualisiert oder gelöscht

5.2. OPTIMISTIC_INCREMENT ( SCHREIBEN )

OPTIMISTIC_INCREMENT und WRITE sind wie zuvor Synonyme, erstere sind jedoch vorzuziehen.

OPTIMISTIC_INCREMENT muss die gleichen Bedingungen erfüllen wie der OPTIMISTIC-Sperrmodus . Außerdem wird der Wert eines Versionsattributs erhöht. Es ist jedoch nicht festgelegt, ob dies sofort erfolgen soll oder bis zum Festschreiben oder Löschen verschoben werden soll.

Es ist wichtig zu wissen, dass ein Persistenzanbieter die OPTIMISTIC_INCREMENT- Funktionalität bereitstellen darf , wenn der OPTIMISTIC-Sperrmodus angefordert wird.

6. Verwenden von Optimistic Locking

We should remember that for versioned entities optimistic locking is available by default. Yet there are several ways of requesting it explicitly.

6.1. Find

To request optimistic locking we can pass the proper LockModeType as an argument to find method of EntityManager:

entityManager.find(Student.class, studentId, LockModeType.OPTIMISTIC);

6.2. Query

Another way to enable locking is using the setLockMode method of Query object:

Query query = entityManager.createQuery("from Student where id = :id"); query.setParameter("id", studentId); query.setLockMode(LockModeType.OPTIMISTIC_INCREMENT); query.getResultList()

6.3. Explicit Locking

We can set a lock by calling EnitityManager's lock method:

Student student = entityManager.find(Student.class, id); entityManager.lock(student, LockModeType.OPTIMISTIC);

6.4. Refresh

We can call the refresh method the same way as the previous method:

Student student = entityManager.find(Student.class, id); entityManager.refresh(student, LockModeType.READ);

6.5. NamedQuery

The last option is to use @NamedQuery with the lockMode property:

@NamedQuery(name="optimisticLock", query="SELECT s FROM Student s WHERE s.id LIKE :id", lockMode = WRITE)

7. OptimisticLockException

When the persistence provider discovers optimistic locking conflicts on entities, it throws OptimisticLockException. We should be aware that due to the exception the active transaction is always marked for rollback.

It's good to know how we can react to OptimisticLockException. Conveniently, this exception contains a reference to the conflicting entity. However, it's not mandatory for the persistence provider to supply it in every situation. There is no guarantee that the object will be available.

There is a recommended way of handling the described exception, though. We should retrieve the entity again by reloading or refreshing. Preferably in a new transaction. After that, we can try to update it once more.

8. Conclusion

In this tutorial, we got familiar with a tool which can help us orchestrate concurrent transactions. Optimistic locking uses version attributes included in entities to control concurrent modifications on them.

Therefore, it ensures that any updates or deletes won't be overwritten or lost silently. Opposite to pessimistic locking, it doesn't lock entities on the database level and consequently, it isn't vulnerable to DB deadlocks.

Wir haben erfahren, dass das optimistische Sperren für versionierte Entitäten standardmäßig aktiviert ist. Es gibt jedoch verschiedene Möglichkeiten, dies explizit anzufordern, indem verschiedene Sperrmodustypen verwendet werden.

Eine weitere Tatsache, an die wir uns erinnern sollten, ist, dass wir jedes Mal, wenn es zu widersprüchlichen Aktualisierungen von Entitäten kommt, eine OptimisticLockException erwarten sollten .

Schließlich ist der Quellcode dieses Tutorials auf GitHub verfügbar.