Transações JDBC em Java
Controle transações de banco de dados em Java JDBC com setAutoCommit, commit, rollback e savepoints.
Uma transação agrupa vários comandos SQL em uma unidade tudo-ou-nada: ou todas as alterações são confirmadas, ou nenhuma é. O exemplo clássico é uma transferência bancária — debitar uma conta, creditar outra — onde aplicar uma sem a outra criaria ou destruiria dinheiro. O JDBC controla transações por meio do objeto Connection.
Este capítulo aborda como abrir uma transação desativando o auto-commit, como commit() e rollback() funcionam, como savepoints permitem desfazer parte de uma transação e como os níveis de isolamento equilibram consistência e concorrência.
Auto-commit está ativado por padrão
Uma conexão recém-criada roda em modo auto-commit: cada comando é confirmado no instante em que termina. Isso é adequado para comandos isolados, mas incorreto para unidades com vários comandos. Para abrir uma transação, desative o 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() torna permanentes e visíveis para outros todas as alterações desde o início da transação. rollback() as descarta todas. A regra de ouro: confirme em caso de sucesso, reverta em qualquer exceção. Esquecer o rollback deixa a conexão segurando bloqueios e uma transação incompleta.
Um exemplo de transferência
A transferência usa dois objetos PreparedStatement, executa ambos os UPDATEs e só confirma depois que os dois foram executados. Se qualquer um lançar uma exceção, o catch reverte tudo para que nenhum dinheiro seja criado ou perdido:
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;
}Savepoints: rollback parcial
Um Savepoint é um marcador dentro de uma transação para o qual você pode reverter, desfazendo trabalho posterior enquanto mantém o trabalho anterior. Isso é útil quando uma etapa opcional dentro de uma unidade maior pode falhar, mas você não quer abandonar a transação inteira:
Savepoint sp = conn.setSavepoint("afterDebit");
// ... risky step ...
conn.rollback(sp); // undo back to the savepoint, not the whole transactionDuas regras para lembrar: reverter para um savepoint não encerra a transação — você ainda precisa de commit() ou de um rollback() completo depois — e você deve liberar savepoints desnecessários com conn.releaseSavepoint(sp) para liberar recursos do banco de dados. Savepoints só existem enquanto o auto-commit está desativado.
Níveis de isolamento
setTransactionIsolation(...) equilibra consistência e concorrência. Do mais fraco ao mais forte: READ_UNCOMMITTED (vê linhas "sujas" não confirmadas de outras transações), READ_COMMITTED (o padrão comum), REPEATABLE_READ (releituras veem as mesmas linhas) e SERIALIZABLE (as transações se comportam como se fossem executadas uma de cada vez). Níveis mais fortes evitam mais anomalias, mas permitem menos paralelismo.
As anomalias que cada nível protege, em ordem:
- Dirty read — ler uma linha que outra transação alterou mas não confirmou. Prevenido a partir de
READ_COMMITTED. - Non-repeatable read — reler a mesma linha e obter um valor diferente porque outra transação confirmou uma alteração nesse intervalo. Prevenido a partir de
REPEATABLE_READ. - Phantom read — executar novamente a mesma consulta e obter linhas extras porque outra transação inseriu linhas correspondentes. Prevenido apenas por
SERIALIZABLE.
Defina o nível antes do início do trabalho e verifique o que seu banco de dados realmente suporta com DatabaseMetaData.supportsTransactionIsolationLevel(...) — nem todo driver respeita todos os níveis.
Um exemplo completo: níveis de isolamento e a unidade de trabalho
Este programa imprime as quatro constantes de nível de isolamento em ordem crescente de força, depois modela uma transferência como uma unidade atômica — preparando duas atualizações e confirmando ambas ou revertendo ambas — espelhando o ciclo setAutoCommit/commit/rollback sem um banco de dados real.
O que extrair da execução:
- As constantes de isolamento crescem em força:
READ_UNCOMMITTED(1),READ_COMMITTED(2),REPEATABLE_READ(4),SERIALIZABLE(8). Você passa uma delas asetTransactionIsolationpara escolher quanta anomalia de concorrência tolerará. - A transferência prepara ambas as atualizações antes de confirmar. Essa é a essência de uma transação: os dois
UPDATEs formam uma unidade, e ocommit()só ocorre quando ambos estão prontos. - O passo
commit()aqui move ambos os comandos dependingparacommittedjuntos — modelando como o banco de dados torna todas as alterações duráveis e visíveis no mesmo instante, nunca pela metade. - O
catchlimpapending— o substituto dorollback(). A lição é a disciplina: qualquer exceção dentro da transação deve levar a um rollback, ou você deixa o banco de dados em um estado parcialmente aplicado. transfer applied: truereflete uma confirmação bem-sucedida. Inverta a lógica para que a guarda falhe e você verá um rollback sem nada aplicado — exatamente a garantia tudo-ou-nada que uma transação existe para oferecer.
Prática
Tópicos relacionados
- Conexão JDBC — onde vivem auto-commit, commit e rollback.
- PreparedStatement — a forma segura de executar atualizações parametrizadas dentro de uma transação.
- Processamento em lote — agrupe muitos comandos; lotes e transações frequentemente trabalham juntos.
- DatabaseMetaData — consulte quais níveis de isolamento seu driver suporta.