Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring, Hibernate transactions. Joining a transaction in Thread B created in A. Possible?

Is it possible to use a transaction in another thread?

Like pass a transaction created in thread A and then execute some logic in Thread B within the same transaction?

I have two queues and separate executors which handles population of certain Entity types.

However, a batch job is managing both population and waiting for each to finish. It would be unnecessary to create two transactions. If one fails, ideally I'd want all data to be rolled back so it would have been ideal to run them as one transaction as well as it provides improved performance.

So, is it possible to create one transaction, pass it along to another thread, execute some stuff within the boundaries of the first one?

I am using Spring and Hibernate and currently using

TransactionTemplate template = new TransactionTemplate( getTransactionManager() );
template.setPropagationBehavior(propagationBehavior);
template.setReadOnly(readOnly);

TransactionStatus status = getTransactionManager().getTransaction(template);

to create a transaction, not using annotations at all and have no plans to do so either.

like image 684
mjs Avatar asked Dec 19 '22 04:12

mjs


2 Answers

It's not possible with Spring. All the transaction based code eventually ends up in TransactionSynchronizationManager which is full of ThreadLocals and no way to copy those values from one thread to another.

If you want to be able to do this, you can use Spring to get the DataSource but you have to create your own connections and manually create Hibernate sessions. Spring's PlatformTransactionManager is out of the question.

[EDIT] What's the point of having several threads in your case? If you want to parallelize work, then the correct approach is to have N threads which prepare the data that should be inserted into the database and then a single thread which creates 1 transaction and then does all the work.

Yes, that means you need to keep a lot of data in memory.

If you really don't want to do that, then the next solution is to have work tables where each thread puts their results. When both threads finish, you start another thread which locks the work tables and runs a few SQL queries to copy the data to the correct place.

Always keep in mind that Database connections, SQL and threads don't mix. A database is global state. If you change global state from several places at the same time, you'll always face all kind of odd problems. Try to avoid that. Split the work into many small, independent tasks (i.e. which work perfectly fine when each of them has their own transaction). If you can't do that, you need to rethink your design until you can or you can't use threads.

like image 112
Aaron Digulla Avatar answered Mar 23 '23 01:03

Aaron Digulla


In Spring, it is actually possible to transfer the transactional context from one thread to another with a simple trick as below:

  • All thread local variables related the transactional context on thread 1 must be retrieved and stored somewhere that can be referred from thread 2's scope. In case of Hibernate, the current org.springframework.orm.hibernate4.SessionHolder should be enough as it also keeps the link to the ongoing transaction.
  • Then, the thread 2 can be started with the following logic integrated at the very beginning of its execution: Binding all thread local variables retrieved in the first step to the same variables on thread 2, using Spring's utilities like org.springframework.transaction.support.TransactionSynchronizationManager.bindResource(Object, Object). After this, the second thread should be able to work in the same Hibernate session and transaction as its parent.

There might be subtle detail that we need to care but the following code should get the job done:

final SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
Runnable newTask = new Runnable() {

        @Override
        public void run() {
            TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
            // Business stuff here.
        }
    };
Executors.newFixedThreadPool(1).submit(newTask);

Hope this helps.

like image 34
Phoenix VN Avatar answered Mar 23 '23 01:03

Phoenix VN