Contents

Hi! In this article we’ll dive into @Transactional working.

@Transactional annotation is used to create database transaction in Spring application. We annotate a method or class and that’s it! Method annotated with @Transactional in executed in database transaction.

@Transactional
void methodExecutedInTransaction() {
   // code executed in transaction
}

Transaction is created thanks to Spring AOP (Aspect Oriented Programming) mechanism. The mechanism creates proxy which wraps method code in transaction. The interceptor used in this process is org.springframework.transaction.interceptor.TransactionInterceptor and have the following method:

@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
  // Work out the target class: may be {@code null}.
  // The TransactionAttributeSource should be passed the target class
  // as well as the method, which may be from an interface.
  Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
  // Adapt to TransactionAspectSupport's invokeWithinTransaction...
  return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

As we can see MethodInvocation object is passed which represents method annotated with @Transational. This method is then passed to another method from parent class. Here is a piece of the method implementation for non-reactive transaction:

Object retVal;
try {
  // This is an around advice: Invoke the next interceptor in the chain.
  // This will normally result in a target object being invoked.
  retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
  // target invocation exception
  completeTransactionAfterThrowing(txInfo, ex);
  throw ex;
}
finally {
  cleanupTransactionInfo(txInfo);
}

Annotated method is called inside try block so whenever an exception is thrown there is an implementation that checks whether this specific exception should cause rollback.

The important thing everybody should remember is that by default the above mechanism is used to provide declarative transactions (there is also possibility to use AspectJ that doesn’t create proxy runtime but it creates aspects in compilation time) and internally called methods in the same class will not create transaction. That’s because only injected bean is the proxy that creates transaction. So if we want to execute code from the same class in transaction, we have two options:

  • extracting this method to another service that will be injected as a proxy with transaction interceptor – usually better and cleaner option
  • lazy self injection – then the proxy is injected with transaction interceptor but looks weird a little bit

@Transactional annotation can be configured by the following properties:

  • transactionManager – qualifier name of transaction manager to be used; this property can be useful when working with many datasources
  • propagation – the transaction propagation type; the possible values are REQUIRED (default value), SUPPORTS, MANDATORY, REQUIRES_NEW, NOT_SUPPORTED, NEVER, NESTED
  • isolation – the transaction isolation level; the possible values are DEFAULT (default value), ISOLATION_READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
  • timeout – the timeout for this transaction (in seconds)
  • readOnly – a boolean flag that can be set to true if the transaction is effectively read-only, allowing for corresponding optimizations at runtime; this property is only hint and doesn’t guarantee any performance increase
  • rollbackFor – exceptions classes that should cause transaction rollback; by default only RuntimeExceptions and Errors cause rollback
  • rollbackForClassName – exceptions classes names that should cause transaction rollback
  • noRollbackFor – exceptions classes that shouldn’t cause transaction rollback
  • noRollbackForClassName – exceptions classes that shouldn’t cause transaction rollback

Propagation is a property that says if a method annotated with @Transactional should be executed in a transaction and specifies conditions of the transaction. There are following possible modes:

  • REQUIRED – supports a current transaction and creates a new one if none exists; this is the default value used in propagation property of @Transactional annotation
  • SUPPORTS – supports a current transaction and executes non-transactionally if none exists
  • MANDATORY – supports a current transaction, throws an exception if none exists
  • REQUIRES_NEW – creates a new transaction, and suspends the current transaction if one exists
  • NOT_SUPPORTED – executes non-transactionally, suspends the current transaction if one exists
  • NEVER – executes non-transactionally, throws an exception if a transaction exists
  • NESTED – executes within a nested transaction if a current transaction exists, behaves like REQUIRED otherwise; actual creation of a nested transaction will only work on specific transaction managers

Isolation property only applies to newly started transactions (see REQUIRES_NEW and REQUIRED propagation modes). It configures concurrent transactions behaviour. Availability of the following options depends on used database. For example Oracle 11g doesn’t support REPEATABLE_READ.

  • DEFAULT – uses the default isolation level of the underlying datastore
  • READ_UNCOMMITED – indicates that dirty reads, non-repeatable reads and phantom reads can occur
  • READ_COMMITTED – indicates that dirty reads are prevented but non-repeatable reads and phantom reads can occur
  • REPEATABLE_READ – indicates that dirty reads and non-repeatable reads are prevented but phantom reads can occur
  • SERIALIZABLE – indicates that dirty reads, non-repeatable reads and phantom reads are prevented

In this article we understood what is @Transactional annotation in Spring, how does it work and how to configure its behaviour. Hope you enjoyed!