Spring Transaction Management Over Multiple Threads — DZone Java

Dulaj Atapattu
5 min readSep 8, 2019

--

The Spring framework provides a comprehensive API for database transaction management. Spring takes care of all underlying transaction management considerations and provides a consistent programming model for different transaction APIs such as Java Transaction API (JTA), JDBC, Hibernate, Java Persistence API (JPA), and Java Data Objects (JDO). There are two main types of transaction management in Spring: declarative transaction management, which is a high level one, and programmatic transaction management, which is more advanced but flexible.

The Spring API works very well with almost all of the transaction management requirements as long as the transaction is on a single thread. The problem arises when we want to manage a transaction across multiple threads. Spring doesn’t support transactions over multiple threads out of the box. Spring doesn’t explicitly mention that in the documentation, but you will end up with runtime errors or unexpected results if you try to do so.

Why Do Spring Transactions Over Multiple Threads Fail?

Spring stores a set of thread locals inside the org.springframework.transaction.support.TransactionSynchronizationManager class. These thread locals are specific for an ongoing transaction on a single thread (Thread locals values are specific for a single thread. Thread local value set by one thread cannot be accessed by another thread).

If we start the transaction from one thread and try to commit or rollback the transaction from another thread, a runtime error will be generated complaining that the Spring transaction is not active on the current thread. Though we start and end the transaction from the same thread, we cannot perform database operations belong to transaction from another thread too.

When we initialize the transaction the actualTransactionActivethread local is set to true. synchronizations thread local is also initialized. Other thread locals are also accessed and updated during the lifecycle of the transaction. When we try to commit or rollback the transaction at the end of the transaction scope, values of these thread locals are again checked. What happens if we use multiple threads for the transaction is that these thread local values are not visible across multiple threads. Therefore, Spring cannot maintain the transaction state throughout the transaction.

How to Use Spring Transactions With Multiple Threads

Now you understand the problem with spring transactions over multiple threads. The thread local values are not propagating to new threads from old threads. The only solution here is to manually copy these thread local values to newly spawned threads to keep the transaction unbroken.

As mentioned above, actualTransactionActivethread local is used to check whether the transaction is active on the current thread. This thread local is set true on the thread which initializes the transaction. But, since this thread local value is not visible to other threads, we have to maintain another boolean flag to inform the activeness of the transaction to other threads. Then we can check that flag from the new thread and set actualTransactionActive thread local to true manually. Thread local value can be set by calling following code line:

TransactionSynchronizationManager.setActualTransactionActive(true);

Practical Applications of Multi-Threaded Database Transactions

The database processing element of Adroitlogic Project-X is a good industrial level practical application of multi-threaded database transactions.

What Is Project-X?

Project X is the base framework for a new generation of redesigned integration products by Adroitlogic. Project-X consists of all the API definitions, core implementations of those APIs, messaging engine, message format definitions and implementations, metrics engine, etc. to be used by those integration products.

What Is a Database Processing Element?

The database connector and the database processing element provide the data persistent capabilities to Project-x. There are three main components to perform database operations: db egress connector, db ingress connector, and the db processing element. Database processing element supports for all four CRUD operations. Database egress connector provides create, update, delete operations and database ingress connector provides read operation in a timed manner.

How Transactions Are Used in The Database Processor

Let’s see how to configure an integration flow to perform a database transactional operation. Here we enter the data to a table, obtain the id of the last inserted row, and then again insert data to another table with the id obtained from the previous database operation.

Suppose there is a database table named as people having columns named as id, name, and age. The second table is named as students and the columns are id, school, and grade. Here, the id of the students table is a foreign key referenced from the people table.

The database transaction scope start element[1] starts the transaction scope. All the database operations happening within the transaction scope are guaranteed to be committed if and only if all the operations within the transaction scope were a success. Then the clone message processing element[2] just takes a clone of the message and sends one copy as the response to the nio http ingress connector and the other copy to the database processor. Two new threads are created here and thread locals of the TransactionSynchronizationManagerclass are copied to the newly created threads. Then, the database processing element[3] does the first insert to the people table in the database. Next, database processing element[5] reads the last inserted row from the people table and returns the result in DBResultsSetFormat. Then we use a custom processing element[6] to extract the id from the DBResultsSetFormat and set it to the header of the message. The last database processing element, element[7], does the second insert to the students table using the data read from the payload the id read from the message header. Finally, the database transaction scope end element closes the transaction scope and commit the changes to the database if all the operations are complete. The database transaction will be rolled back in any kind of failure within the transaction scope of the flow.

Originally published at https://dzone.com.

--

--

Dulaj Atapattu
Dulaj Atapattu

Written by Dulaj Atapattu

Senior Software Engineer | Full Stack Developer | DevOps Engineer