W3docs

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 transaction

Due 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_COMMITTED in 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_READ in 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.

java— editable, runs on the server

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 a setTransactionIsolation per 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 UPDATE sono un'unica unità, e il commit() avviene solo quando entrambi sono pronti.
  • Il passaggio commit() qui sposta entrambe le istruzioni da pending a committed insieme — modellando come il database rende tutte le modifiche durevoli e visibili nello stesso istante, mai a metà.
  • Il catch svuota pending — il sostituto di rollback(). 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: true riflette 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

Pratica
Hai impostato conn.setAutoCommit(false), eseguito due istruzioni UPDATE e la seconda lancia una SQLException. Cosa deve fare il tuo blocco catch per preservare l'integrità dei dati?
Hai impostato conn.setAutoCommit(false), eseguito due istruzioni UPDATE e la seconda lancia una SQLException. Cosa deve fare il tuo blocco catch per preservare l'integrità dei dati?

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.
Was this page helpful?