Gestione delle Transazioni con JDBC

Con il termine transazione si intende una unità logica di elaborazione composta da una sequenza di operazioni che devono terminare tutte con successo o fallimento. Un apposito modulo del DBMS, denominato Transaction Manager (TM), ha il compito di coordinare tutte le operazioni affinché le transazioni soddisfino le proprietà ACID di Atomicità, Consistenza, Isolamento e Durabilità. Tali proprietà hanno lo scopo di assicurare che lo stato del database sia sempre consistente, indipendentemente dalle operazioni eseguite e dal loro esito.

Le situazioni che il Transaction Manager deve gestire sono generalmente di due tipo:

  1. Anomalie dovute all’accesso concorrente ai dati da parte di due transazioni T1 e T2. In funzione del tipo di operazioni eseguite si riconoscono tre possibili situazioni di conflitto: write-write, write-read e read-write.
  2. Deadlock dovute a lock dei dati in fase di scrittura e lettura dei dati.

Lo standard SQL non prevede l’uso esplicito dei lock, la cui gestione è demandata quindi al Transaction Manager. Questo al fine di garantire al TM il controllo completo della situazione.

Supporto alle Transazioni

Nella specifica JDBC tutti i servizi per il supporto e la gestione delle transazioni sono localizzati a livello dell’interfaccia java.sql.Connection. In tale interfaccia troviamo infatti sia i metodi per il commit ed il rollback della transazione che per la definizione del livello di isolamento delle operazioni.

Autocommit

La specifica JDBC prevede, per default, che ad ogni esecuzione di uno statement SQL sia eseguita una operazione di commit in modo automatico. Tale operazione automatica è eseguita direttamente dall’oggetto Connection ad ogni comando SQL. Per gestire tale comportamento l’interfaccia mette a disposizione i metodo setAutoCommit() e getAutoCommit(). In particolare l’oggetto è creato con auto-commit a TRUE e quindi non sarà possibile racchiudere più istruzioni SQL in una unica transazione a meno che non si esegua il metodo setAutoCommit() passando FALSE come valore. In questo caso la transazione deve essere committata o abortita esplicitamente dall’utente attraverso l’invocazione dei metodi commit() e rollback().

Commit e Rollback

Una volta che l’oggetto Connection è posto in modalità non auto-commit, è possibile realizzare transazioni complesse, ovvero composte da un numero generico di comandi SQL. Come detto sarà compito del programmatore decidere quando una transazione deve essere terminata con successo oppure quando deve essere abortita.

Nella gestione delle transazioni si deve comunque tenere presente che l’isolamento è a livello della connessione, quindi l’oggetto Connecition non deve essere condiviso tra processi che intendono eseguire operazioni concorrenti sulla base dati.

Il seguente codice mostra un esempio di gestione esplicita dei commit/abort:

Livello di Isolamento

Il livello di isolamento di una transazione definisce il grado di isolamento delle operazioni eseguite rispetto a modifiche apportate alla base dati da altre transazioni. Lo scopo è quello di prevenire in tutto o in parte le anomalie che sono state elencate nell’introduzione e che sono:

  • perdite di aggiornamenti (lost updates o anomalie write-write);
  • letture non ripetibili (non-repeatable reads o anomalie read-write);
  • letture improprie (phantom read o anomalie write-read).

I livelli di isolamento vanno dal più basso, in cui non vi è alcuna garanzia, al più alto in cui tutte le transazioni sono completamente isolate le une dalle altre. Naturalmente i livelli più alti offrono maggiori garanzia ma richiedono anche un numero maggiore di lock sulle risorse che devono essere acquisiti per eseguire le operazioni, con un impatto diretto sulle prestazioni e sul grado di concorrenza dell’applicazione.

La specifica JDBC offre i metodi setTransactionIsolation() e getTransactionIsolation() per gestire l’isolamento. In particolare il metodo setter riceve un parametro intero i cui lavori ammessi sono definiti da costanti presenti direttamente nell’interfaccia Connection. Ovviamente non è invocabile a transazione iniziata.

TRANSACTION_NONE Le transazioni non sono supportate. E’ equivalente ad avere auto-commit abilitato.
TRANSACTION_READ_UNCOMMITTED Nessun livello di isolamento garantito, quindi tutte le possibili anomalie si possono verificare.
TRANSACTION_READ_COMMITTED Vengono prevenute solo le perdite di aggiornamenti.
TRANSACTION_REPEATABLE_READ Vengono prevenute le perdite di aggiornamenti e le letture non ripetibili.
TRANSACTION_SERIALIZABLE Tutte le anomalie sono prevenute essendo il livello massimo di isolamento.