Careeroza
Transactions
ACID and Auto-commit
By default a JDBC connection is in auto-commit mode: every statement is its own transaction. When several statements must succeed or fail together (debit one account, credit another), turn auto-commit off, then commit() or rollback().
| ACID | Meaning |
|---|---|
| Atomicity | all statements or none |
| Consistency | constraints hold before and after |
| Isolation | concurrent transactions do not see each other's half-done work |
| Durability | committed data survives a crash |
Money Transfer
public void transfer(long fromId, long toId, BigDecimal amount) throws SQLException {
try (Connection con = dataSource.getConnection()) {
con.setAutoCommit(false);
con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
try (PreparedStatement debit = con.prepareStatement(
"UPDATE account SET balance = balance - ? WHERE id = ? AND balance >= ?");
PreparedStatement credit = con.prepareStatement(
"UPDATE account SET balance = balance + ? WHERE id = ?")) {
debit.setBigDecimal(1, amount); debit.setLong(2, fromId); debit.setBigDecimal(3, amount);
if (debit.executeUpdate() != 1) throw new SQLException("Insufficient balance");
credit.setBigDecimal(1, amount); credit.setLong(2, toId);
credit.executeUpdate();
con.commit(); // both updates become visible together
} catch (SQLException e) {
con.rollback(); // undo the debit
throw e;
}
}
}Tip: In Spring you write @Transactional on the service method instead; Spring does the setAutoCommit/commit/rollback around it.
Isolation Levels
| Level | Dirty read | Non-repeatable read | Phantom read |
|---|---|---|---|
| READ_UNCOMMITTED | possible | possible | possible |
| READ_COMMITTED (PostgreSQL default) | no | possible | possible |
| REPEATABLE_READ (MySQL InnoDB default) | no | no | possible* |
| SERIALIZABLE | no | no | no |
* InnoDB prevents most phantoms with next-key locks.