Spring @Transactional

Transaction in JAVA: from JDBC to Spring @Transactional and TransactionTemplate

In this reading, we will understand how transactions are handled in a plain old JDBC transaction management and, later, how Spring Framework’s declarative transaction management is made possible with Spring aspect-oriented programming (AOP) (official Spring documentation). In the final part, we are also going to talk about TransactionTemplate!

From this post, we started to read about transactions. We learned that a transaction is a single logical unit of work, handled by DBMS using different properties (e.g. ACID) to maintain consistency in a database.

How plain JDBC Transaction Management works

Computer science, both in practice and theory, includes different levels of abstraction. The various libraries and the various Java frameworks manage explicit DBMS transactions with different abstraction mechanisms, but all these will still have to go through the JDBC management.

import java.sql.Connection;

Connection connection = dataSource.getConnection(); /* 1 */

try (connection) {
    connection.setAutoCommit(false); /* 2 */
    connection.commit(); /* 3.A */

} catch (SQLException e) {
    connection.rollback(); /* 3.B */
}

In the code portion above, we can see how a plain JDBC transaction is handled:

  1. We need a connection using a DataSource.
  2. We can customize this behaviour by calling setAutoCommit(false) to turn off the auto-commit mode and call the commit()/rollback() to indicate the end of a transaction.
  3. When we call commit(), it means complete/close the current transaction. Thus, since rollback() undoes any changes in the current, it will effectively do nothing.

How to use Spring’s @Transactional annotation

public class PostService {

    @Transactional
    public Post savePost(Post post) {
        // store the post into db
        // update the informations related to the Author
        // modify some parameters of entities related to post
        return persistedPost;
    }
}

As we can see in the code portion above, it is not required to open or close database connections explicitly (try-finally). Instead, we use @Transactional.

We also do not have to catch SQLExceptions, as Spring converts these exceptions to runtime exceptions for us. And we have better integration into the Spring ecosystem. @Transactional will use a TransactionManager internally, which will use a data source. The last one is @Bean, which we have to specify in our Spring context configuration, but we don’t have to worry about later.

There are potential pitfalls we may encounter, so the following cautions need to be paid:

  • Spring Configuration must be annotated with the @EnableTransactionManagement annotation (In Spring Boot, this is done automatically), and we need to specify a TransactionManager on the Spring configuration class:
@Configuration
@EnableTransactionManagement
public class SpringGlobalConfig {

     @Bean
     public PlatformTransactionManager transactionManager() {
         return transactionManagerImplementation;
     }

}
  • Spring offers us a PlatformTransactionManager or TransactionManager interface, which, by default, comes with several handy implementations. In our Spring configuration, as a concrete TransactionManager implementation, we will use a HibernateTransactionManager (if using plain Hibernate) or JpaTransactionManager (if using Hibernate through JPA).
  • Methods that we annotate with @Transactional must be Public.
  • In Spring, we can’t simply call a @Transactional method from the same instance, because the AOP-Proxy: @Transactional tells Spring to create a proxy around the object. The proxy intercepts call to the object from other objects. The proxy does not intercept calls within the object.

Solving the “AOP-Proxy” problem

We can solve this situation using one of these tips:

  • Self-Injections (from Spring 4).
  • A functional interface (from Java 8) Supplier.
  • AspectJ.

1 Self-Injections

@Repository
class MyRepository {

    @Autowired
    MyRepository mySelfInjectedRepository;

    @Transactional
    void methodInTransactional() {
        // some logic here
    }

    void plainMethod () {
        // some logic here
        mySelfInjectedRepository.methodInTransactional();
        // some logic here
    }
}

As we can read above, in this example, we have a @Repository class that uses itself as a resource to call the @Transactional method in the “plain” method. That will work because we have two instances of the same class in this case.

This solution could be used if we are using the Spring 4+ version. Is not so elegant, let’s see another one!

2 Functional interface Supplier

@Service
public class UserService {

    @Autowired
    private TransactionHandler transactionHandler;

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            transactionHandler.runInTransaction(() -> addUser(user.getUsername, user.getPassword));
        }
    }

    private boolean addUser(String username, String password) {
        // TODO call userRepository
    }
}

@Service
public class TransactionHandler {

    @Transactional(propagation = Propagation.REQUIRED)
    public <T> T runInTransaction(Supplier<T> supplier) {
        return supplier.get();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public <T> T runInNewTransaction(Supplier<T> supplier) {
        return supplier.get();
    }
}

The key to this implementation is the class TransactionHandler, which provides two methods that are going to handle transactions. With the help of the Supplier class, we will use the “addUser” method to save massively a list of users in transactional.

3 Aspect J

@SpringBootApplication
@EnableTransactionManagement(mode=AdviceMode.ASPECTJ)
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.ENABLED)

AdviceMode.ASPECTJ” means that it will use AspectJ instead of CGLib/JDK dynamic proxies for the transaction management/cache functionality. AspectJ doesn’t do anything at runtime as the classes are compiled directly with aspects.

Spring AOP is a proxy-based AOP framework. It’ll create proxies of that object to implement aspects to the target objects. We have two ways of achieving that:

  1. JDK dynamic proxy: is the preferred way for Spring AOP. This method will be used if the targeted object implements even one interface.
  2. CGLib proxy: if the target object doesn’t implement an interface, then the CGLib proxy can be used.

Let’s have a look at the official docs for more information.

Detecting if Spring is using transaction

TransactionSynchronizationManager.isActualTransactionActive()

Using the static method “isActualTransactionActive” of the class TransactionSynchronizationManager, we can detect if Spring uses a transaction in a specific code block.

Another way could be changing the log level with the property: logging.level.org.springframework.transaction.interceptor = TRACE

Enabling the log level to “TRACE”, we are going to see logs as follows:

2022-07-17 14:45:07,162 TRACE - Getting transaction for [com.Class.method]

2022-07-17 14:45:07,273 TRACE - Completing transaction for [com.Class.method]

@Transactional settings

PropertyTypeDescription
valueStringIs an optional qualifier that specifies the transaction manager to be used.
transactionManagerStringAlias for value.
labelArray of String labels to add an expressive description to the transaction.Labels may be evaluated by transaction managers to associate implementation-specific behaviour with the actual transaction.
propagationenumPropagationOptional propagation setting.
isolationenumIsolationOptional isolation level. Applies only to propagation values of REQUIRED or REQUIRES_NEW.
timeoutint (in seconds of granularity)How long the transaction can run before timing out and being rolled back automatically by the underlying transaction infrastructure. Applies only to propagation values of REQUIRED or REQUIRES_NEW.
timeoutStringString (in seconds of granularity)Alternative for specifying the timeout in seconds as a String value — for example, as a placeholder.
readOnlybooleanRead-write versus read-only transaction. Only applicable to values of REQUIRED or REQUIRES_NEW.
rollbackForAn array of Class objects, which must be derived from Throwable.Optional array of exception types that must cause a rollback.
rollbackForClassNameAn array of exception name patterns.Optional array of exception name patterns that must cause a rollback.
noRollbackForAn array of Class objects, which must be derived from Throwable.Optional array of exception types that must not cause a rollback.
noRollbackForClassNameAn array of exception name patterns.Optional array of exception name patterns that must not cause a rollback.

By default, rollback only happens for runtime, unchecked exceptions (RuntimeException and Error). The checked exception does not trigger a rollback of the transaction. With the “rollbackFor/rollbackForClassName”  annotation parameter, we can change this behaviour and also handle checked exceptions.

With the “noRollbackFor/noRollbackForClassName” annotation parameter is possible to say to Spring to not cause a rollback for the specified exceptions.

In a real-life scenario, we can use the “readOnly” property to use two data sources: a writer and read-only. If we need a connection for only reading data and not modifying, with “readOnly” set to “true”, we gonna improve the connection setup.

Using the TransactionTemplate

Based on a callback approach, with the “execute” method exposed by the “TransationTemplate” class is possible to execute our operations in a single transaction.

public class SimpleService implements Service {

    // single TransactionTemplate shared amongst all methods in this instance
    private final TransactionTemplate transactionTemplate;

    // use constructor-injection to supply the PlatformTransactionManager
    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public Object updateUser() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try{
                    updateUser();
                    updateRelatedEntities();
                } catch(SomeSpecificException someException){
                    log.error("...");
                    status.setRollbackOnly();
                }
            }
        });
    }
}

Note that we have used the method “doInTransactionWithoutResult”, which will not return a result outside. Otherwise, we can use the “doInTransaction” method if we need an operation result.

With the “setRollbackOnly” method, we will request a rollback in the case we have encountered a “SomeSpecificExcepton“.

Pay attention: if that method is called, all the next operations after the catch block, inside the “execute” method, will be rolled back!


Thanks for reading. Stay tuned!

Learn. Grow. Teach.

Subscribe
Notify of
0 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x