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().

ACIDMeaning
Atomicityall statements or none
Consistencyconstraints hold before and after
Isolationconcurrent transactions do not see each other's half-done work
Durabilitycommitted 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

LevelDirty readNon-repeatable readPhantom read
READ_UNCOMMITTEDpossiblepossiblepossible
READ_COMMITTED (PostgreSQL default)nopossiblepossible
REPEATABLE_READ (MySQL InnoDB default)nonopossible*
SERIALIZABLEnonono

* InnoDB prevents most phantoms with next-key locks.