Pessimistisches Sperren in JPA

1. Überblick

Es gibt viele Situationen, in denen wir Daten aus einer Datenbank abrufen möchten. Manchmal möchten wir es für die Weiterverarbeitung sperren, damit niemand anderes unsere Aktionen unterbrechen kann.

Wir können uns zwei Kontrollmechanismen für die Parallelität vorstellen, die es uns erlauben, dies zu tun: das Setzen der korrekten Transaktionsisolationsstufe oder das Sperren von Daten, die wir gerade benötigen. **

Die Transaktionsisolation ist für Datenbankverbindungen definiert. Wir können es so konfigurieren, dass der unterschiedliche Sperrungsgrad erhalten bleibt.

  • Die Isolationsstufe wird jedoch festgelegt, sobald die Verbindung erstellt wird ** und wirkt sich auf jede Anweisung innerhalb dieser Verbindung aus. Glücklicherweise können wir pessimistische Sperren verwenden, die Datenbankmechanismen verwenden, um einen detaillierteren exklusiven Zugriff auf die Daten zu reservieren.

Wir können eine pessimistische Sperre verwenden, um sicherzustellen, dass keine anderen Transaktionen reservierte Daten ändern oder löschen können.

Es gibt zwei Arten von Sperren, die wir beibehalten können: eine exklusive Sperre und eine gemeinsam genutzte Sperre. Wir könnten Daten lesen, aber nicht schreiben, wenn jemand anderes eine gemeinsame Sperre besitzt. Um die reservierten Daten zu ändern oder zu löschen, benötigen wir eine exklusive Sperre.

Wir können exklusive Sperren mit den Anweisungen SELECT …​ FOR UPDATE erwerben.

2. Sperrmodi

Die JPA-Spezifikation definiert drei pessimistische Sperrmodi, die wir besprechen werden:

  • PESSIMISTIC READ__ - erlaubt uns, eine gemeinsame Sperre zu erhalten und die

Daten werden nicht aktualisiert oder gelöscht ** PESSIMISTIC WRITE__ - Erlaubt uns eine exklusive Sperre und

verhindern, dass die Daten gelesen, aktualisiert oder gelöscht werden ** PESSIMISTIC FORCE INCREMENT - funktioniert wie PESSIMISTIC WRITE__ und so

erhöht zusätzlich ein Versionsattribut einer versionierten Entität

Alle sind statische Member der Klasse LockModeType und ermöglichen Transaktionen, eine Datenbanksperre abzurufen. Sie werden alle beibehalten, bis die Transaktion festgeschrieben oder rückgängig gemacht wird.

  • Es ist erwähnenswert, dass wir jeweils nur eine Sperre erhalten können. Wenn dies nicht möglich ist, wird eine PersistenceException ausgelöst. **

2.1. PESSIMISTIC READ __

Wann immer wir nur Daten lesen möchten und keine fehlerhaften Lesevorgänge auftreten, können wir PESSIMISTIC READ__ (Shared Lock) verwenden. Wir können jedoch keine Aktualisierungen oder Löschungen vornehmen.

Es kommt manchmal vor, dass die von uns verwendete Datenbank die Sperre PESSIMISTIC READ nicht unterstützt. Daher kann es sein, dass wir stattdessen die Sperre PESSIMISTIC WRITE erhalten.

2.2. PESSIMISTIC WRITE __

Jede Transaktion, die eine Sperre für Daten erlangen und Änderungen daran vornehmen muss, sollte die Sperre PESSIMISTIC WRITE erhalten. Gemäß der JPA -Spezifikation verhindert das Halten der PESSIMISTIC WRITE -Sperre, dass andere Transaktionen die Daten lesen, aktualisieren oder löschen.

  • Bitte beachten Sie, dass einige Datenbanksysteme https://en.wikipedia.org/wiki/Multiversion concurrency control[multi-version-Parallelitätssteuerung implementieren], mit der Leser bereits blockierte Daten abrufen können. **

2.3. PESSIMISTIC FORCE INCREMENT

Diese Sperre funktioniert ähnlich wie PESSIMISTIC WRITE , wurde jedoch zur Zusammenarbeit mit versionierten Entitäten eingeführt - Entitäten, deren Attribut mit @ Version__ versehen ist.

Vor Aktualisierungen von versionierten Entitäten könnte der Abruf der Sperre PESSIMISTIC FORCE INCREMENT vorausgehen. Wenn Sie diese Sperre erwerben, wird die Versionsspalte aktualisiert.

Es ist Sache eines Persistenzanbieters, zu bestimmen, ob er PESSIMISTIC FORCE INCREMENT für nicht versionierte Entitäten unterstützt oder nicht. Wenn dies nicht der Fall ist, wird die __PersistanceException ausgelöst.

2.4. Ausnahmen

Es ist gut zu wissen, welche Ausnahme beim Arbeiten mit pessimistischem Sperren auftreten kann. Die Spezifikation JPA bietet verschiedene Arten von Ausnahmen:

  • PessimisticLockException - Gibt an, dass eine Sperre oder erhalten wird

Das Konvertieren einer gemeinsamen in eine exklusive Sperre schlägt fehl und führt zu einem Rollback auf Transaktionsebene ** LockTimeoutException - gibt an, dass eine Sperre oder erhalten wird

Konvertieren einer gemeinsamen Sperre in eine exklusive Zeitüberschreitung und führt zu einer Rollback auf Anweisungsebene ** PersistanceException – zeigt ein Persistenzproblem an

aufgetreten. PersistanceException und ihre Subtypen mit Ausnahme von NoResultException , NonUniqueResultException, LockTimeoutException und ____QueryTimeoutException markieren die aktive Transaktion als zurückgesetzt.

3. Verwenden von pessimistischen Sperren

Es gibt verschiedene Möglichkeiten, eine pessimistische Sperre für einen einzelnen Datensatz oder eine Gruppe von Datensätzen zu konfigurieren.

3.1. Finden

Es ist wahrscheinlich der einfachste Weg. Es reicht aus, ein LockModeType -Objekt als Parameter an die find -Methode zu übergeben:

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

3.2. Abfrage

Außerdem können wir auch ein Query -Objekt verwenden und den setLockMode -Setter mit einem Sperrmodus als Parameter aufrufen:

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

3.3. Explizites Sperren

Es ist auch möglich, die von der Suchmethode abgerufenen Ergebnisse manuell zu sperren:

Student resultStudent = entityManager.find(Student.class, studentId);
entityManager.lock(resultStudent, LockModeType.PESSIMISTIC__WRITE);

3.4. Aktualisierung

Wenn wir den Zustand der Entität mit der _refresh _ -Methode überschreiben möchten, können wir auch eine Sperre setzen:

Student resultStudent = entityManager.find(Student.class, studentId);
entityManager.refresh(resultStudent, LockModeType.PESSIMISTIC__FORCE__INCREMENT);

3.5. NamedQuery

Mit der Annotation @ NamedQuery können wir auch einen Sperrmodus festlegen:

@NamedQuery(name="lockStudent",
  query="SELECT s FROM Student s WHERE s.id LIKE :studentId",
  lockMode = PESSIMISTIC__READ)

4. Bereich sperren

  • Der Parameter für den Sperrbereich definiert, wie mit Sperrbeziehungen der gesperrten Entität verfahren werden soll.

Zur Konfiguration des Bereichs können Sie PessimisticLockScope enum verwenden. Es enthält zwei Werte: NORMAL und EXTENDED .

Wir können den Gültigkeitsbereich festlegen, indem Sie den Parameter " javax.persistance.lock.scope " mit dem PessimisticLockScope -Wert als Argument an die richtige Methode von EntityManager , Query , TypedQuery oder NamedQuery übergeben:

Map<String, Object> properties = new HashMap<>();
map.put("javax.persistence.lock.scope", PessimisticLockScope.EXTENDED);

entityManager.find(
  Student.class, 1L, LockModeType.PESSIMISTIC__WRITE, properties);

4.1. PessimisticLockScope.NORMAL

  • Wir sollten wissen, dass PessimisticLockScope.NORMAL der Standardbereich ist. ** Mit diesem Sperrbereich sperren wir die Entität selbst. Wenn es mit verknüpfter Vererbung verwendet wird, werden auch die Vorfahren gesperrt.

Schauen wir uns den Beispielcode mit zwei Entitäten an:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Person {

    @Id
    private Long id;
    private String name;
    private String lastName;

   //getters and setters
}

@Entity
public class Employee extends Person {

    private BigDecimal salary;

   //getters and setters
}

Wenn wir eine Sperre für den Employee erhalten möchten, können wir die SQL -Abfrage beobachten, die diese beiden Entitäten umfasst:

SELECT t0.ID, t0.DTYPE, t0.LASTNAME, t0.NAME, t1.ID, t1.SALARY
FROM PERSON t0, EMPLOYEE t1
WHERE ((t0.ID = ?) AND ((t1.ID = t0.ID) AND (t0.DTYPE = ?))) FOR UPDATE

4.2. PessimisticLockScope.EXTENDED

Der EXTENDED -Geltungsbereich deckt die gleiche Funktionalität ab wie NORMAL. Außerdem können verwandte Entitäten in einer Join-Tabelle blockiert werden

Einfach ausgedrückt, es funktioniert mit Entitäten, die mit @ ElementCollection oder @ OneToOne , @ OneToMany usw. mit @ JoinTable versehen sind.

Schauen wir uns den Beispielcode mit der Annotation @ ElementCollection an:

@Entity
public class Customer {

    @Id
    private Long customerId;
    private String name;
    private String lastName;
    @ElementCollection
    @CollectionTable(name = "customer__address")
    private List<Address> addressList;

   //getters and setters
}

@Embeddable
public class Address {

    private String country;
    private String city;

   //getters and setters
}

Analysieren wir einige Abfragen, wenn Sie nach der Customer -Entität suchen:

SELECT CUSTOMERID, LASTNAME, NAME
FROM CUSTOMER WHERE (CUSTOMERID = ?) FOR UPDATE

SELECT CITY, COUNTRY, Customer__CUSTOMERID
FROM customer__address
WHERE (Customer__CUSTOMERID = ?) FOR UPDATE

Wir sehen, dass es zwei 'FOR UPDATE'-Abfragen gibt, die eine Zeile in der Kundentabelle sowie eine Zeile in der Join-Tabelle sperren.

  • Eine andere interessante Tatsache, die wir beachten sollten, ist, dass nicht alle Persistenzanbieter Sperrbereiche unterstützen. **

5. Einstellen des Sperrzeitlimits

Neben der Einstellung von Sperrbereichen können Sie einen weiteren Sperrparameter einstellen - Timeout. Der Timeout-Wert ist die Anzahl der Millisekunden, die auf den Erhalt einer Sperre gewartet werden soll, bis die LockTimeoutException auftritt.

Wir können den Wert des Timeouts ähnlich wie für Sperrbereiche ändern, indem Sie die Eigenschaft "__javax.persistence.lock.timeout" mit der richtigen Anzahl von Millisekunden verwenden.

Es ist auch möglich, das Sperren ohne Wartezeit festzulegen, indem der Timeout-Wert auf Null gesetzt wird. Wir sollten jedoch berücksichtigen, dass es Datenbanktreiber gibt, die das Festlegen eines Timeout-Werts auf diese Weise nicht unterstützen.

Map<String, Object> properties = new HashMap<>();
map.put("javax.persistence.lock.timeout", 1000L);

entityManager.find(
  Student.class, 1L, LockModeType.PESSIMISTIC__READ, properties);

6. Schlussfolgerung

Wenn die Einstellung der richtigen Isolationsstufe nicht ausreicht, um gleichzeitige Transaktionen zu bewältigen, erhalten Sie mit der JPA ein pessimistisches Sperren. Dadurch können wir verschiedene Transaktionen isolieren und koordinieren, damit sie nicht gleichzeitig auf dieselbe Ressource zugreifen.

Um dies zu erreichen, können wir zwischen den besprochenen Sperrentypen wählen und deren Parameter wie deren Gültigkeitsbereich oder Timeouts ändern.

Andererseits sollten wir daran denken, dass das Verständnis von Datenbanksperren ebenso wichtig ist wie das Verständnis der Mechanismen zugrunde liegender Datenbanksysteme. Es ist auch wichtig zu wissen, dass das Verhalten von pessimistischen Sperren von dem Persistenzanbieter abhängt, mit dem wir arbeiten.

Schließlich ist der Quellcode dieses Tutorials auf GitHub für hibernate und für https://github.com/eugenp/verfügbar . tutorials/tree/master/persistenzmodule/spring-data-eclipselink/src/test/java/com/baeldung/eclipselink/springdata/pessimisticlocking[EclipseLink].