9

I am new to JDBC and I am trying to update 2 tables in my database, so I would like to do it in 1 transaction so if one query fails, the other should also fail. I would like to provide such behaviour or just have an opportunity to do rollback if one of them fails.

Here are my 2 queries:

int i = stmt.executeUpdate("INSERT INTO product (title, price, `status`) " +
                "VALUES ( \"" + product.getTitle() + "\", " + product.getPrice() + ", " + product.getStatus().ordinal() + ");");
int j = stmt.executeUpdate("INSERT INTO product_categories (product_id, category_id) " +
                "VALUES (last_insert_id(), " + categoryId + ");");

1 Answer 1

21

If you want to execute multiple statements atomically, you need to use a transaction. A JDBC connection defaults to 'auto-commit' mode, which means each statement is executed in its own transaction. So you first need to disable auto-commit mode, using Connection.setAutoCommit(false).

With auto-commit mode disabled, executed statements will be executed in the current transaction, if there is no current transaction, one will be started. This transaction can then either be committed using Connection.commit() or rolled back using Connection.rollback().

You will need to do something like:

try (Connection connection = DriverManager.getConnection(...)) {
    connection.setAutoCommit(false);
    try (Statement stmt = connection.createStatement()) {
        stmt.executeUpdate(<your first update>);
        stmt.executeUpdate(<your second update>);

        connection.commit();
    } catch (SQLException e) {
        connection.rollback();
        throw e;
    }
}

For more details, see the JDBC tutorial chapter Using Transactions.

And please learn about prepared statements. Concatenating values into a query string is bad, because it can lead to SQL injection or weird errors if you forget to escape values. See also the JDBC tutorial chapter Using Prepared Statements.

7
  • This line "connection.setAutoCommit(false);" should be within try catch block Commented Jan 7, 2018 at 12:52
  • 5
    Additionally, a good practice is to add "con.setAutoCommit(true);" at finally block. Commented Jan 7, 2018 at 13:04
  • @MaksymOvsianikov No, it doesn't need to, unless you want to restore the auto commit back to true. Why do you think that is required? And may I remind you that this is a simple example, not a full-fledged real-world use. Commented Jan 7, 2018 at 13:25
  • 1
    @Mark Rotteveel, I didn't wrote it's required, I wrote it's "a good practice" :) check this Oracle tutorial: docs.oracle.com/javase/tutorial/jdbc/basics/transactions.html Commented Jan 18, 2018 at 22:38
  • This is a great answer! I think it would be good to also mention that the database needs to be set to a particular isolation level. For example, if Postgres was set to the "Read uncommitted" isolation level it would actually be possible for a query to see the inserted product before the transaction is committed, which would violate most users' expectations of atomicity. Commented Jan 31, 2021 at 21:01

Not the answer you're looking for? Browse other questions tagged or ask your own question.