7.7. Working with Transactions

By default, jOOQ runs in a mode that commits transactions automatically. It starts a new transaction for each SQL statement and commits the transaction if there are no errors in the execution of the statement. The transaction is rolled back if an error occurs.

The default transaction has the following parameters: READ_WRITE | READ_COMMITTED | REC_VERSION | WAIT, the same parameters that are used by the JDBC driver. You can change the default isolation mode using the parameters of the connection pool — see BasicDataSource.setDefaultTransactionIsolation in the getDataSource method of the JooqConfig configuration class.

7.7.1. Explicit Transactions

In jOOQ you have several ways to control transactions explicitly. Since we are going to develop our application using the Spring Framework, we will use the transaction manager specified in the configuration (JooqConfig). You can get the transaction manager by declaring the txMgr property in the class as follows:

  1. @Autowired
  2. private DataSourceTransactionManager txMgr;

The standard scenario for using this technique with a transaction would be coded like this:

  1. TransactionStatus tx = txMgr.getTransaction(new DefaultTransactionDefinition());
  2. try {
  3. // actions in the context of a transaction
  4. for (int i = 0; i < 2; i++)
  5. dsl.insertInto(BOOK)
  6. .set(BOOK.ID, 5)
  7. .set(BOOK.AUTHOR_ID, 1)
  8. .set(BOOK.TITLE, "Book 5")
  9. .execute();
  10. // transaction commit
  11. txMgr.commit(tx);
  12. }
  13. catch (DataAccessException e) {
  14. // transaction rollback
  15. txMgr.rolback(tx);
  16. }

However, Spring enables that scenario to be implemented much more easily using the @Transactional annotation specified before the method of the class. Thereby, all actions performed by the method will be wrapped in the transaction.

  1. /**
  2. * Delete customer
  3. *
  4. * @param customerId
  5. */
  6. @Transactional(propagation = Propagation.REQUIRED,
  7. isolation = Isolation.REPEATABLE_READ)
  8. public void delete(int customerId) {
  9. this.dsl.deleteFrom(CUSTOMER)
  10. .where(CUSTOMER.CUSTOMER_ID.eq(customerId))
  11. .execute();
  12. }

Transaction Parameters

Propagation

The propagation parameter defines how to work with transactions if our method is called from an external transaction.

  • Propagation.REQUIRED

    execute in the existing transaction if there is one. Otherwise, create a new one.

    Propagation.MANDATORY

    execute in the existing transaction if there is one. Otherwise, raise an exception.

    Propagation.SUPPORTS

    execute in the existing transaction if there is one. Otherwise, execute outside the transaction.

    Propagation.NOT_SUPPORTED

    always execute outside the transaction. If there is an existing one, it will be suspended.

    Propagation.REQUIRES_NEW

    always execute in a new independent transaction. If there is an existing one, it will be suspended until the new transaction is ended.

    Propagation.NESTED

    if there is an existing transaction, execute in a new so-called “nested” transaction. If the nested transaction is rolled back, it will not affect the external transaction; if the external transaction is rolled back, the nested one will be rolled back as well. If there is no existing transaction, a new one is simply created.

    Propagation.NEVER

    always execute outside the transaction. Raise an exception if there is an existing one.

Isolation Level

The isolation parameter defines the isolation level. Five values are supported: DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE. If the DEFAULT value of the isolation parameter is specified, that level will be used.

The other isolation levels are taken from the SQL standard, not all of them supported exactly by Firebird. Only the READ_COMMITED level corresponds in all of the criteria, so JDBC READ_COMMITTED is mapped into read_committed in Firebird. REPEATABLE_READ is mapped into concurrency (SNAPSHOT) and SERIALIZABLE is mapped into consistency (SNAPSHOT TABLE STABILITY).

Firebird supports additional transaction parameters besides isolation level, viz. NO RECORD_VERSION/RECORD_VERSION (applicable only to a transaction with READ COMMITTED isolation) and WAIT/NO WAIT. The standard isolation levels can be mapped to Firebird transaction parameters by specifying the properties of the JDBC connection (see more details in the Using Transactions chapter of Jaybird 2.1 JDBC driver Java Programmer’s Manual).

If your transaction works with more than one query, it is recommended to use the REPEATABLE_READ isolation level to maintain data consistency.

Read Mode

By default, a transaction is in the read-write mode. The readOnly property in the @Transactional annotation can be used to specify that it is to be read-only.