Transazioni JDBC in Java
Gestisci le transazioni database in Java JDBC con setAutoCommit, commit, rollback e savepoint.
Una transazione raggruppa più istruzioni SQL in un'unica unità tutto-o-niente: o tutte le modifiche vengono confermate, oppure nessuna lo è. L'esempio classico è un bonifico bancario — addebitare un conto, accreditarne un altro — dove applicare una sola operazione senza l'altra farebbe perdere o creare denaro. JDBC controlla le transazioni tramite l'oggetto Connection.
Questo capitolo spiega come aprire una transazione disabilitando l'auto-commit, come funzionano commit() e rollback(), come i savepoint permettono di annullare solo una parte di una transazione, e come i livelli di isolamento bilanciano coerenza e concorrenza.
L'auto-commit è attivo per impostazione predefinita
Una nuova connessione opera in modalità auto-commit: ogni istruzione viene confermata non appena termina. Va bene per istruzioni singole, ma non per unità multi-istruzione. Per aprire una transazione, disabilita l'auto-commit:
conn.setAutoCommit(false); // begin a transaction
try {
// ... several statements ...
conn.commit(); // make all changes permanent
} catch (SQLException e) {
conn.rollback(); // undo everything since the last commit
throw e;
} finally {
conn.setAutoCommit(true); // restore default for the pool
}commit e rollback
commit() rende permanenti e visibili agli altri tutte le modifiche apportate dall'inizio della transazione. rollback() le annulla tutte. La regola d'oro: commit in caso di successo, rollback per qualsiasi eccezione. Dimenticare il rollback lascia la connessione con i lock attivi e una transazione a metà.
Un trasferimento di esempio
Il trasferimento utilizza due oggetti PreparedStatement, esegue entrambi gli UPDATE e effettua il commit solo quando entrambi sono stati eseguiti. Se uno dei due lancia un'eccezione, il catch annulla tutto per evitare che venga creato o perso denaro:
conn.setAutoCommit(false);
try (PreparedStatement debit = conn.prepareStatement(
"UPDATE acct SET bal = bal - ? WHERE id = ?");
PreparedStatement credit = conn.prepareStatement(
"UPDATE acct SET bal = bal + ? WHERE id = ?")) {
debit.setBigDecimal(1, amount); debit.setInt(2, fromId); debit.executeUpdate();
credit.setBigDecimal(1, amount); credit.setInt(2, toId); credit.executeUpdate();
conn.commit();
} catch (SQLException e) {
conn.rollback();
throw e;
}Savepoint: rollback parziale
Un Savepoint è un marcatore all'interno di una transazione fino al quale è possibile eseguire il rollback, annullando il lavoro successivo pur mantenendo quello precedente. Questo è utile quando un passaggio opzionale all'interno di un'unità più grande potrebbe fallire, ma non si vuole abbandonare l'intera transazione:
Savepoint sp = conn.setSavepoint("afterDebit");
// ... risky step ...
conn.rollback(sp); // undo back to the savepoint, not the whole transactionDue regole da ricordare: eseguire il rollback fino a un savepoint non termina la transazione — occorre comunque commit() o un rollback() completo in seguito — e bisogna rilasciare i savepoint non più necessari con conn.releaseSavepoint(sp) per liberare le risorse del database. I savepoint esistono solo quando l'auto-commit è disattivato.
Livelli di isolamento
setTransactionIsolation(...) bilancia coerenza e concorrenza. Dal più debole al più forte: READ_UNCOMMITTED (vede le righe "sporche" non ancora confermate dagli altri), READ_COMMITTED (il valore predefinito comune), REPEATABLE_READ (le riletture restituiscono le stesse righe) e SERIALIZABLE (le transazioni si comportano come se fossero eseguite una alla volta). I livelli più forti prevengono più anomalie ma riducono il parallelismo.
Le anomalie prevenute da ciascun livello, in ordine:
- Dirty read — leggere una riga che un'altra transazione ha modificato ma non ancora confermato. Prevenuta da
READ_COMMITTEDin poi. - Non-repeatable read — rileggere la stessa riga e ottenere un valore diverso perché un'altra transazione ha confermato una modifica nel frattempo. Prevenuta da
REPEATABLE_READin poi. - Phantom read — rieseguire la stessa query e ottenere righe aggiuntive perché un'altra transazione ha inserito righe corrispondenti. Prevenuta solo da
SERIALIZABLE.
Imposta il livello prima di iniziare il lavoro e verifica cosa supporta effettivamente il tuo database con DatabaseMetaData.supportsTransactionIsolationLevel(...) — non tutti i driver rispettano ogni livello.
Esempio completo: livelli di isolamento e unità di lavoro
Questo programma stampa le quattro costanti del livello di isolamento in ordine crescente di forza, poi modella un trasferimento come unità atomica — preparando due aggiornamenti e confermandoli entrambi o annullandoli entrambi — riproducendo il ciclo setAutoCommit/commit/rollback senza un database reale.
Cosa ricavare dall'esecuzione:
- Le costanti di isolamento crescono in forza:
READ_UNCOMMITTED(1),READ_COMMITTED(2),REPEATABLE_READ(4),SERIALIZABLE(8). Si passa una di queste asetTransactionIsolationper scegliere quante anomalie di concorrenza si è disposti a tollerare. - Il trasferimento prepara entrambi gli aggiornamenti prima di confermarli. Questa è l'essenza di una transazione: i due
UPDATEsono un'unica unità, e ilcommit()avviene solo quando entrambi sono pronti. - Il passaggio
commit()qui sposta entrambe le istruzioni dapendingacommittedinsieme — modellando come il database rende tutte le modifiche durevoli e visibili nello stesso istante, mai a metà. - Il
catchsvuotapending— il sostituto dirollback(). La lezione è la disciplina: qualsiasi eccezione all'interno della transazione deve portare a un rollback, altrimenti il database viene lasciato in uno stato parzialmente applicato. transfer applied: trueriflette un commit riuscito. Invertendo la logica in modo che il controllo fallisca, si vedrebbe un rollback senza nulla applicato — esattamente la garanzia tutto-o-niente che una transazione fornisce.
Esercitati
Argomenti correlati
- JDBC Connection — dove risiedono auto-commit, commit e rollback.
- PreparedStatement — il modo sicuro per eseguire gli aggiornamenti parametrizzati all'interno di una transazione.
- Batch Processing — raggruppa molte istruzioni; batch e transazioni spesso lavorano insieme.
- DatabaseMetaData — interroga quali livelli di isolamento supporta il tuo driver.