Database transactions

The database transaction is very important part of your application because it defines consistency. It is important to have control over it and do not rely on some frameworks like Spring.

But you can use Spring and have better transaction control.

There is an example, how to control database transaction in Java. DbTransaction class calls ROLLBACK automatically if COMMIT was not called.

@Service
public class ExampleDbTransaction {
@Autowired
DbTransaction dbTransaction;

public void exampleDatabaseTransaction() {
 //outside of transaction if the transaction is not nested
 try(DbTransaction.Status status = dbTransaction.openNewTransaction()){
        //inside transaction
        status.commitTransaction();
    }
 }
}

Sometimes we want to nest transaction. A method defines a transaction but the inner method also defines a transaction. Mostly what we need is the transaction nesting – only one transaction is active (the outer transaction).

The implementation of DbTransaction service:

@Service
public class DbTransaction {

@Autowired
private PlatformTransactionManager transactionManager;

//each thread has counter of nested transactions
private static ThreadLocal<AtomicInteger> transactionCheck = ThreadLocal.withInitial(()->new AtomicInteger());

public class Status implements AutoCloseable {

private TransactionStatus transactionStatus;

private Status(TransactionStatus transactionStatus) {
this.transactionStatus = transactionStatus;
}

/**
* Transaction commit
*/
public void commitTransaction() {
transactionManager.commit(this.transactionStatus);
}

/**
* Always call this method in final block
*/
@Override
public void close() {
transactionCheck.get().decrementAndGet();
if(!this.transactionStatus.isCompleted()) {
//commit was not called, so rollback it now
transactionManager.rollback(this.transactionStatus);
}
}
}

/**
* Starts a new database transaction. Always call close on returned status.
* @return
*/
public Status openNewTransaction() {
Status status = new Status(transactionManager.getTransaction(new DefaultTransactionDefinition()));
transactionCheck.get().incrementAndGet();
return status;
}

/**
* Returns true if current thread is in database transaction
* @return
*/
public static boolean isInTransaction() {
return transactionCheck.get().intValue() != 0;
}
}

The advantage of this approach is that we have an overview of how the transaction works.

However there is a danger. When a developer forgets to use try block, the database transaction is not closed (commit nor rollback is called). This leads to errors that are difficult to detect. I recommend to check transaction status in @Aspect class. When developer forgets to close the transaction, the error is written to the console.

//test if we have all database transaction closed
if (DbTransaction.isInTransaction()) {
//we should not be in database transaction now
log.error("STILL IN DATABASE TRANSACTION!! method:" + method.getName());
}

One thought on “Database transactions”

Leave a Reply