Blocco ottimistico in JPA

1. Introduzione

Quando si tratta di applicazioni aziendali, è fondamentale gestire correttamente l'accesso simultaneo a un database. Ciò significa che dovremmo essere in grado di gestire più transazioni in modo efficace e, soprattutto, a prova di errore.

Inoltre, dobbiamo garantire che i dati rimangano coerenti tra letture e aggiornamenti simultanei.

Per ottenere ciò possiamo utilizzare il meccanismo di blocco ottimistico fornito dall'API Java Persistence. Ciò implica che più aggiornamenti effettuati contemporaneamente sugli stessi dati non interferiscono tra loro.

2. Comprensione del blocco ottimistico

Per utilizzare il blocco ottimistico, è necessario disporre di un'entità che includa una proprietà con l' annotazione @Version . Durante l'utilizzo, ogni transazione che legge i dati mantiene il valore della proprietà version.

Prima che la transazione voglia effettuare un aggiornamento, controlla di nuovo la proprietà della versione.

Se il valore è cambiato nel frattempo, viene generata un'eccezione OptimisticLockException . In caso contrario, la transazione esegue il commit dell'aggiornamento e incrementa una proprietà della versione del valore.

3. Blocco pessimistico contro blocco ottimistico

È bello sapere che, a differenza del blocco ottimistico, JPA ci offre un blocco pessimistico. È un altro meccanismo per la gestione dell'accesso simultaneo ai dati.

Copriamo il blocco pessimistico in uno dei nostri articoli precedenti, il blocco pessimistico in JPA. Scopriamo qual è la differenza e come possiamo trarre vantaggio da ogni tipo di blocco.

Come abbiamo detto prima, il blocco ottimistico si basa sul rilevamento delle modifiche sulle entità controllando il loro attributo di versione . Se si verifica un aggiornamento simultaneo, si verifica OptmisticLockException . Dopodiché, possiamo riprovare ad aggiornare i dati.

Possiamo immaginare che questo meccanismo sia adatto per applicazioni che eseguono molte più letture che aggiornamenti o eliminazioni. Inoltre, è utile in situazioni in cui le entità devono essere staccate per un po 'di tempo e non è possibile mantenere i blocchi.

Al contrario, il meccanismo di blocco pessimistico implica il blocco di entità a livello di database.

Ogni transazione può acquisire un blocco sui dati. Finché mantiene il blocco, nessuna transazione può leggere, eliminare o effettuare aggiornamenti sui dati bloccati. Possiamo presumere che l'utilizzo del blocco pessimistico possa causare deadlock. Tuttavia, garantisce una maggiore integrità dei dati rispetto al blocco ottimistico.

4. Attributi della versione

Gli attributi della versione sono proprietà con annotazione @Version . Sono necessari per abilitare il blocco ottimistico. Vediamo una classe di entità di esempio:

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

Ci sono diverse regole che dovremmo seguire durante la dichiarazione degli attributi di versione:

  • ogni classe di entità deve avere un solo attributo di versione
  • deve essere posizionato nella tabella primaria per un'entità mappata a più tabelle
  • il tipo di un attributo di versione deve essere uno dei seguenti: int , Integer , long , Long , short , Short , java.sql.Timestamp

Dovremmo sapere che possiamo recuperare un valore dell'attributo di versione tramite entità, ma non dobbiamo aggiornarlo o incrementarlo. Solo il provider di persistenza può farlo, quindi i dati rimangono coerenti.

Vale la pena notare che i provider di persistenza sono in grado di supportare il blocco ottimistico per entità che non hanno attributi di versione. Tuttavia, è una buona idea includere sempre gli attributi della versione quando si lavora con il blocco ottimistico.

Se proviamo a bloccare un'entità che non contiene tale attributo e il provider di persistenza non lo supporta, risulterà una PersitenceException .

5. Modalità di blocco

JPA ci fornisce due diverse modalità di blocco ottimistico (e due alias):

  • OTTIMISTICO : ottiene un blocco di lettura ottimistico per tutte le entità contenenti un attributo di versione
  • OPTIMISTIC_FORCE_INCREMENT - ottiene un blocco ottimistico uguale a OPTIMISTIC e incrementa ulteriormente il valore dell'attributo della versione
  • LEGGI - è sinonimo di OTTIMISTA
  • SCRIVI - è un sinonimo di OPTIMISTIC_FORCE_INCREMENT

Possiamo trovare tutti i tipi elencati sopra nella classe LockModeType .

5.1. OTTIMISTA ( LEGGI )

Come già sappiamo, le modalità di blocco OPTIMISTIC e READ sono sinonimi. Tuttavia, la specifica JPA ci consiglia di utilizzare OPTIMISTIC nelle nuove applicazioni.

Ogni volta che richiediamo la modalità di blocco OTTIMISTICA , un provider di persistenza impedirà ai nostri dati di letture sporche e letture non ripetibili .

In parole povere, dovrebbe garantire che qualsiasi transazione non riesca a commettere alcuna modifica sui dati che un'altra transazione:

  • è stato aggiornato o eliminato ma non è stato eseguito il commit
  • è stato aggiornato o eliminato correttamente nel frattempo

5.2. OPTIMISTIC_INCREMENT ( WRITE )

Come in precedenza, OPTIMISTIC_INCREMENT e WRITE sono sinonimi, ma è preferibile il primo.

OPTIMISTIC_INCREMENT deve soddisfare le stesse condizioni della modalità di blocco OPTIMISTIC . Inoltre, incrementa il valore di un attributo di versione. Tuttavia, non è specificato se deve essere eseguito immediatamente o può essere rimandato fino al commit o allo scaricamento.

Vale la pena di sapere che un provider di persistenza è autorizzato a fornire OPTIMISTIC_INCREMENT funzionalità quando OTTIMISTA viene richiesta la modalità di blocco.

6. Utilizzo del blocco ottimistico

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.

Abbiamo appreso che il blocco ottimistico è abilitato per le entità con versione per impostazione predefinita. Tuttavia, esistono diversi modi per richiederlo in modo esplicito utilizzando vari tipi di modalità di blocco.

Un altro fatto che dovremmo ricordare è che ogni volta che ci sono aggiornamenti in conflitto sulle entità, dovremmo aspettarci un'eccezione OptimisticLockException .

Infine, il codice sorgente di questo tutorial è disponibile su GitHub.