The documentation about using transactions with jdbc suggests the following code
public void updateCoffeeSales(HashMap<String, Integer> salesForWeek)
throws SQLException {
PreparedStatement updateSales = null;
PreparedStatement updateTotal = null;
String updateString =
"update " + dbName + ".COFFEES " +
"set SALES = ? where COF_NAME = ?";
String updateStatement =
"update " + dbName + ".COFFEES " +
"set TOTAL = TOTAL + ? " +
"where COF_NAME = ?";
try {
con.setAutoCommit(false);
updateSales = con.prepareStatement(updateString);
updateTotal = con.prepareStatement(updateStatement);
for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) {
updateSales.setInt(1, e.getValue().intValue());
updateSales.setString(2, e.getKey());
updateSales.executeUpdate();
updateTotal.setInt(1, e.getValue().intValue());
updateTotal.setString(2, e.getKey());
updateTotal.executeUpdate();
con.commit();
}
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
if (con != null) {
try {
System.err.print("Transaction is being rolled back");
con.rollback();
} catch(SQLException excep) {
JDBCTutorialUtilities.printSQLException(excep);
}
}
} finally {
if (updateSales != null) {
updateSales.close();
}
if (updateTotal != null) {
updateTotal.close();
}
con.setAutoCommit(true);
}
}
However, the error handling seems wrong to me?
If there is a NullPointerException inside the try block, it will not be caught. Instead, execution will go straight to the finally block, where it will call con.setAutoCommit(true)
, which according the documentation will commit any transactions in progress. It seems that this is obviously not the intended behavior, as it commits an incomplete transaction.
I would think that this might just be a bug in the example, but other tutorials also forget to catch exceptions besides SqlException ( further example ).
Am I misunderstanding what's going on?
Before calling con.setAutoCommit(true)
, the example closes the possibly outstanding prepared statements. As such, those statements will not get executed when switching back to auto-commit mode. No incomplete transactions will get committed there.
I checked the first tutorial you linked, it makes the mistake of not using a finally block, not cancelling the outstanding statements, and not restoring the auto-commit mode. That seems really careless.
In general, I recommend to stick to the official tutorials, or to sources you really really trust.
I'm inclined to think you're right; my solution to this was to write the following class some time ago which correctly rolls-back when an unexpected exception is raised. As a bonus, you get to wrap transactions up in a try-with-resources block which I find much more readable and easy to work with.
You use it like so:
try(Transaction trans = Transaction.create(conn)) {
// execute multiple queries, use trans.getConn()
trans.commit();
} catch (SQLException e) {
// Handle exception, transaction is safely rolled-back and closed
}
// No need for a finally block, or to catch RuntimeException.
Here's the full class:
import static com.google.common.base.Preconditions.checkState;
import java.sql.Connection;
import java.sql.SQLException;
/**
* A Transaction can be used in a try-with-resources block to ensure a set of queries are
* executed as a group.
*
* try(Transaction trans = Transaction.create(conn)) {
* // execute multiple queries, use trans.getConn()
* trans.commit();
* } catch (SQLException e) {
* // Handle exception, transaction is safely rolled-back and closed
* }
*/
public final class Transaction implements AutoCloseable {
private Connection conn;
private boolean committed = false;
private boolean rolledback = false;
/**
* Create a Transaction on the current connection, use to create
* a try-with-resources block.
*
* Note that if a transaction is started while another transaction is
* ongoing (i.e. conn.getAutoCommit() == true) the earlier transaction
* is committed.
*/
public static Transaction start(Connection conn) throws SQLException {
return new Transaction(conn);
}
private Transaction(Connection conn) throws SQLException {
this.conn = conn;
// this is a no-op if we're not in a transaction, it commits the previous transaction if we are
this.conn.setAutoCommit(true);
this.conn.setAutoCommit(false);
}
/**
* Call once all queries in the transaction have been executed,
* to indicate transaction is complete and ready to be committed.
* Should generally be the last line in the try block.
*/
public void commit() throws SQLException {
if(committed) {
throw new SQLException("Cannot commmit a transaction more than once");
}
if(rolledback) {
throw new SQLException("Cannot commit a previously rolled-back transaction");
}
committed = true;
getConn().commit();
}
/**
* Call explicitly to cancel the transaction, called implicitly
* if commit() is not called by the time the Transaction should
* be closed.
*/
public void rollback() throws SQLException {
if(rolledback) {
throw new SQLException("Cannot rollback a transaction more than once");
}
if(committed) {
throw new SQLException("Cannot rollback a previously committed transaction");
}
rolledback = true;
getConn().rollback();
}
/**
* Should not be called directly, called in the try-with-resources
* finally block to close the transaction.
*/
@Override
public void close() throws SQLException {
try {
if(!committed && !rolledback) {
conn.rollback();
throw new SQLException("Should explicitly rollback or commit transaction, rolling-back");
}
} finally {
conn.setAutoCommit(true);
conn = null;
}
}
/**
* Returns the Connection being used for this transaction. You are encouraged
* to use this method to access the transactional connection while inside the
* transaction's try-with-resources block.
*/
public Connection getConn() {
checkState(conn != null, "Connection has already been closed");
return conn;
}
}
I haven't yet open-sourced the project this class is a part of, but I'd be happy to explicitly release this under an MIT license if anyone needs it.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.