The SQL Savepoint is a feature provided by JDBC to manage transactions effectively. A Savepoint can be used to create a partial rollback in a transaction to a specific point, instead of rolling back the entire transaction.
The Savepoint feature comes in quite handy when dealing with complex transaction management. In a complex transaction scenario, you would have many SQL statements executed. If something were to go wrong and an Exception occurs after several SQL statements have executed, it would make sense to only rollback the transaction to a certain point where no errors occurred, instead of rolling back the entire transaction.
For example, letβs say we have a transaction that involves transferring funds from one account to another. The transaction may involve the following SQL statements:
1. Check if the source account has sufficient balance
2. Deduct the transfer amount from the source account
3. Add the transfer amount to the destination account
4. Update the transaction log
If there is an error while updating the transaction log, it would be pointless to rollback the entire transaction. The first two SQL statements would have already executed, and it would make sense to rollback the transaction only to the point before the third statement executes. This is where Savepoint comes into play.
Here is an example of using Savepoint in a JDBC transaction:
Connection conn = null;
Savepoint savepoint = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost/database", "username", "password");
conn.setAutoCommit(false); // Disable auto-commit
// Execute first SQL statement
PreparedStatement stmt1 = conn.prepareStatement("SELECT balance FROM accounts WHERE account_no = ?");
stmt1.setString(1, "source_account_no");
ResultSet rs = stmt1.executeQuery();
// Check if the source account has sufficient balance
if (rs.next() && rs.getInt("balance") >= transferAmount) {
// Deduct the transfer amount from the source account
PreparedStatement stmt2 = conn.prepareStatement("UPDATE accounts SET balance = balance - ? WHERE account_no = ?");
stmt2.setInt(1, transferAmount);
stmt2.setString(2, "source_account_no");
stmt2.executeUpdate();
// Add the transfer amount to the destination account
PreparedStatement stmt3 = conn.prepareStatement("UPDATE accounts SET balance = balance + ? WHERE account_no = ?");
stmt3.setInt(1, transferAmount);
stmt3.setString(2, "destination_account_no");
stmt3.executeUpdate();
// Savepoint before updating the transaction log
savepoint = conn.setSavepoint();
// Update the transaction log
PreparedStatement stmt4 = conn.prepareStatement("INSERT INTO transaction_log (source_account_no, destination_account_no, transfer_amount) VALUES (?, ?, ?)");
stmt4.setString(1, "source_account_no");
stmt4.setString(2, "destination_account_no");
stmt4.setInt(3, transferAmount);
stmt4.executeUpdate();
}
conn.commit();
} catch (SQLException e) {
if (savepoint != null) {
conn.rollback(savepoint); // Rollback to savepoint instead of the start
} else {
conn.rollback(); // Rollback the entire transaction
}
} finally {
if (conn != null) {
conn.close();
}
}
In the above example, we create a Savepoint before executing the 4th SQL statement. If an Exception occurs, we will only rollback the transaction to the Savepoint instead of rolling back the entire transaction. This way, the first two SQL statements will still have an effect, and the transfer amount will be deducted from the source account.