Pessimistisk låsning i JPA

1. Oversigt

Der er masser af situationer, hvor vi vil hente data fra en database. Nogle gange ønsker vi at låse det for os selv til videre behandling, så ingen andre kan afbryde vores handlinger.

Vi kan tænke på to samtidige kontrolmekanismer, der giver os mulighed for at gøre det: at indstille det korrekte transaktionsisolationsniveau eller indstille en lås på data, som vi har brug for i øjeblikket.

Transaktionsisolationen er defineret for databaseforbindelser. Vi kan konfigurere det til at bevare den forskellige grad af låsedata.

Imidlertid, isolationsniveauet indstilles, når forbindelsen oprettes og det påvirker alle udsagn inden for den forbindelse. Heldigvis kan vi bruge pessimistisk låsning, der bruger databasemekanismer til at reservere mere detaljeret eksklusiv adgang til dataene.

Vi kan bruge en pessimistisk lås for at sikre, at ingen andre transaktioner kan ændre eller slette reserverede data.

Der er to typer låse, vi kan beholde: en eksklusiv lås og en delt lås. Vi kunne læse, men ikke skrive data, når en anden har en delt lås. For at ændre eller slette de reserverede data skal vi have en eksklusiv lås.

Vi kan erhverve eksklusive låse ved hjælp af 'VÆLG… TIL OPDATERING'Udsagn.

2. Lås tilstande

JPA-specifikation definerer tre pessimistiske låsemetoder, som vi skal diskutere:

  • PESSIMISTIC_READ - giver os mulighed for at få en delt lås og forhindre, at data opdateres eller slettes
  • PESSIMISTIC_WRITE - giver os mulighed for at få en eksklusiv lås og forhindre, at dataene læses, opdateres eller slettes
  • PESSIMISTIC_FORCE_INCREMENT - fungerer som PESSIMISTIC_WRITE og det supplerer desuden en versionattribut for en versioneret enhed

Alle er statiske medlemmer af LockModeType klasse og tillade transaktioner at opnå en databaselås. De bevares alle, indtil transaktionen begår eller ruller tilbage.

Det er værd at bemærke, at vi kun kan få en lås ad gangen. Hvis det er umuligt a PersistenceException kastes.

2.1. PESSIMISTIC_READ

Når vi bare vil læse data og ikke støde på beskidte læsninger, kan vi bruge det PESSIMISTIC_READ (delt lås). Vi kan dog ikke foretage opdateringer eller sletninger.

Det sker undertiden, at den database, vi bruger, ikke understøtter PESSIMISTIC_READ lås, så det er muligt, at vi får PESSIMISTIC_WRITE lås i stedet.

2.2. PESSIMISTIC_WRITE

Enhver transaktion, der skal erhverve en lås på data og foretage ændringer i den, skal få PESSIMISTIC_WRITE låse. Ifølge JPA specifikation, besiddelse PESSIMISTIC_WRITE låsning forhindrer andre transaktioner i at læse, opdatere eller slette dataene.

Bemærk, at nogle databasesystemer implementerer samtidighedskontrol i flere versioner, der gør det muligt for læsere at hente data, der allerede er blokeret.

2.3. PESSIMISTIC_FORCE_INCREMENT

Denne lås fungerer på samme måde som PESSIMISTIC_WRITE, men det blev introduceret for at samarbejde med versionerede enheder - enheder, der har en attribut, der er kommenteret med @Version.

Eventuelle opdateringer af versionerede enheder kan være forud for indhentning af PESSIMISTIC_FORCE_INCREMENT låse. At erhverve denne lås resulterer i opdatering af versionskolonnen.

Det er op til en vedholdenhedsudbyder at afgøre, om den understøtter PESSIMISTIC_FORCE_INCREMENT for uomvendte enheder eller ej. Hvis det ikke gør det, kaster det Persistance undtagelse.

2.4. Undtagelser

Det er godt at vide, hvilken undtagelse der kan forekomme, når du arbejder med pessimistisk låsning. JPA specifikation giver forskellige typer undtagelser:

  • PessimistiskLåseundtagelse - angiver, at opnåelse af en lås eller konvertering af en delt til eksklusiv lås mislykkes og resulterer i tilbageførsel på transaktionsniveau
  • LockTimeoutException - angiver, at opnåelse af en lås eller konvertering af en delt lås til eksklusiv timeout og resulterer i en tilbageførsel på erklæringsniveau
  • PersistanceException - angiver, at der opstod et vedvarende problem. Persistance undtagelse og dens undertyper, undtagen NoResultException, NonUniqueResultException,LockTimeoutExceptionog QueryTimeoutException, markerer den aktive transaktion, der skal rulles tilbage.

3. Brug af pessimistiske låse

Der er et par mulige måder at konfigurere en pessimistisk lås på en enkelt post eller gruppe af poster. Lad os se, hvordan man gør det i JPA.

3.1. Finde

Det er sandsynligvis den mest ligefremme måde. Det er nok at videregive en LockModeType objekt som parameter til finde metode:

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

3.2. Forespørgsel

Derudover kan vi bruge en Forespørgsel objekt også og kalde setLockMode setter med en låsemodus som parameter:

Query query = entityManager.createQuery ("fra Student hvor studentId =: studentId"); query.setParameter ("studentId", studentId); query.setLockMode (LockModeType.PESSIMISTIC_WRITE); query.getResultList ()

3.3. Eksplicit låsning

Det er også muligt at låse resultaterne manuelt ved hjælp af find-metoden:

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

3.4. Opdater

Hvis vi vil overskrive enhedens tilstand af Opdater metode, kan vi også indstille en lås:

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

3.5. NamedQuery

@NamedQuery annotation giver os også mulighed for at indstille en låsemodus:

@NamedQuery (navn = "lockStudent", forespørgsel = "VÆLG s FRA Elev s HVOR s.id LIKE: studentId", lockMode = PESSIMISTIC_READ)

4. Lås rækkevidde

Låseomfangsparameter definerer, hvordan man skal håndtere låseforhold for den låste enhed. Det er muligt at få en lås bare på en enkelt enhed defineret i en forespørgsel eller derudover blokere dens relationer.

For at konfigurere det anvendelsesområde, vi kan bruge PessimistiskLåsSkop enum. Den indeholder to værdier: NORMAL og UDVIDET.

Vi kan indstille omfanget ved at sende en parameter 'javax.persistance.lock.scope'Med PessimistiskLåsSkop værdi som et argument for den korrekte metode til EntityManager, Forespørgsel, TypedQuery eller NamedQuery:

Kortegenskaber = ny HashMap (); map.put ("javax.persistence.lock.scope", PessimisticLockScope.EXTENDED); entityManager.find (Student.class, 1L, LockModeType.PESSIMISTIC_WRITE, egenskaber); 

4.1. PessimisticLockScope.NORMAL

Vi skal vide, at PessimisticLockScope.NORMAL er standardområdet. Med dette låseomfang låser vi selve enheden. Når den bruges med sammenføjet arv, låser den også forfædrene.

Lad os se på prøvekoden med to enheder:

@Entity @Inheritance (strategi = InheritanceType.JOINED) offentlig klasse Person {@Id privat Lang id; privat strengnavn; privat streng efternavn; // getters and setters} @Entity offentlig klasse Medarbejder udvider Person {privat BigDecimal løn; // getters og setters}

Når vi ønsker at få en lås på Medarbejder, kan vi observere SQL forespørgsel, der spænder over disse to enheder:

VÆLG t0.ID, t0.DTYPE, t0.LASTNAME, t0.NAME, t1.ID, t1.SALÆR FRA PERSON t0, MEDARBEJDER t1 HVOR ((t0.ID =?) OG ((t1.ID = t0.ID) OG (t0.DTYPE =?))) FOR OPDATERING

4.2. PessimisticLockScope.UTVIDET

Det UDVIDET omfang dækker den samme funktionalitet som NORMAL. Ud over, det er i stand til at blokere relaterede enheder i en sammenføjningstabel.

Kort sagt, det fungerer med enheder, der er kommenteret med @ElementCollection eller @En til en, @OneToMany osv. med @JoinTable.

Lad os se på prøvekoden med @ElementCollection kommentar:

@Entity offentlig klasse kunde {@Id privat Lang kundeId; privat strengnavn; privat streng efternavn; @ElementCollection @CollectionTable (navn = "kunde_adresse") privat List addressList; // getters and setters} @ Embeddable public class Address {private String country; private String by; // getters og setters}

Lad os analysere nogle forespørgsler, når vi søger efter Kunde enhed:

VÆLG KUNDEID, EFTERNAVN, NAVN FRA KUNDER HVOR (KUNDEID =?) FOR OPDATERING VÆLG BY, LAND, Kunde_KUNDEID FRA kundeadresse HVOR (Kunde_KUNDEID =?) FOR OPDATERING

Vi kan se, at der er to 'TIL OPDATERING'Forespørgsler, der låser en række i kundetabellen såvel som en række i sammenføjningstabellen.

En anden interessant kendsgerning, som vi bør være opmærksomme på, er at ikke alle udholdenhedsudbydere understøtter låseomfang.

5. Indstilling af låsetimeout

Udover at indstille låseomfang kan vi justere en anden låseparameter - timeout. Timeoutværdien er antallet af millisekunder, som vi vil vente på at få en lås, indtil LockTimeoutException opstår.

Vi kan ændre værdien af ​​timeout på samme måde som låseomfang ved at bruge egenskaben 'javax.persistence.lock.timeout ' med det rigtige antal millisekunder.

Det er også muligt at specificere 'no wait' -låsning ved at ændre timeoutværdien til nul. Vi skal dog huske på, at der er databasedrivere, som understøtter ikke indstilling af en timeout-værdi på denne måde.

Kortegenskaber = nyt HashMap (); map.put ("javax.persistence.lock.timeout", 1000L); entityManager.find (Student.class, 1L, LockModeType.PESSIMISTIC_READ, egenskaber);

6. Konklusion

Når det ikke er nok at indstille det rette isolationsniveau til at klare samtidige transaktioner, giver JPA os pessimistisk låsning. Det giver os mulighed for at isolere og orkestrere forskellige transaktioner, så de ikke får adgang til den samme ressource på samme tid.

For at opnå det kan vi vælge mellem diskuterede typer låse og derfor ændre parametre som deres omfang eller timeouts.

På den anden side skal vi huske, at forståelse af databaselåse er lige så vigtigt som at forstå mekanismerne i underliggende databasesystemer. Det er også vigtigt at have i tankerne, at pessimistiske låses opførsel afhænger af udholdenhedsudbyder, vi arbejder med.

Endelig er kildekoden til denne vejledning tilgængelig på GitHub til dvale og til EclipseLink.