Spring, Hibernate transactions. Joining a transaction on stream B created in A. Possibly?

Can I use a transaction on a different thread?

How do I transfer a transaction created on thread A and then execute some logic on Thread B inside the same transaction?

I have two queues and separate executors that handle a collection of specific Entity types.

However, the batch job controls both the population and waits for each to complete. It would not be necessary to create two transactions. If anyone fails, ideally I would like to have all the data discarded, so it would be ideal to run it as a single transaction and also provide improved performance.

So, is it possible to create one transaction , transfer it to another thread, do some things within the first?

I am using Spring and Hibernate and am currently using

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

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

      

to create a transaction without using annotations at all, and do not plan to do so .

+3


source to share


3 answers


This is not possible with Spring. All transaction code eventually ends up with TransactionSynchronizationManager

, which is full ThreadLocal

and does not allow copying these values ​​from one stream to another.

If you want to do this, you can use Spring to get DataSource

, but you need to create your own connections and manually create Hibernate sessions. Spring is PlatformTransactionManager

out of the question.

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



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

If you really don't want to do this, then the next solution would be to have worksheets where each thread puts its results. When both threads finish, you start another thread that locks the work tables and runs multiple SQL queries to copy the data to the correct location.

Always remember that database connections, SQL and streams do not mix. The database is a global state. If you change the global state from multiple locations at the same time, you will always face all the odd problems. Try to avoid this. Divide the work into many small, independent tasks (that is, that work great when each has its own transaction). If you cannot do this, then you need to rethink your design until you can or cannot use streams.

+5


source


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

  • All thread local variables associated with the transactional context in thread 1 must be retrieved and stored somewhere that can be passed from the scope of thread 2. In the case of Hibernate, the current one org.springframework.orm.hibernate4.SessionHolder

    should be sufficient since it also maintains communication with the current transaction.
  • Thread 2 can then be launched with the following logic integrated at the very beginning of its execution: bind all thread local variables obtained in the first step to the same variables in thread 2 using Spring utilities such as org.springframework.transaction.support.TransactionSynchronizationManager.bindResource(Object, Object)

    . After that, the second thread should be running in the same Hibernate session and transaction as parent.

There might be subtle details we need to take care of, 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 it helps.

+2


source


I decided to present my own answer that allows this to happen differently, in all environments and environments, by having other threads delegate work to the original thread.

Example:

public static void main(String[] args) throws Exception {

        System.out.println(Thread.currentThread().getId() + ": (1)");

        JoinLock lock = new JoinLock();
        new Thread(() -> {
                lock.delegate(() -> {
                        System.out.println(Thread.currentThread().getId() + ": (a) ");  // Will execute in the originating thread
                });
        }).start();
        lock.await();

        System.out.println(Thread.currentThread().getId() + ": (2)");
}

      

Outputs:

1: (1)
1: (a) 
1: (2)

      

Another example using passed runnable and some additional details:

public static void main(String[] args) throws Exception {

        System.out.println(Thread.currentThread().getId() + ": (1)");

        new JoinLock().await((lock) -> {

                new Thread(() -> {

                        // Should execute as another thread
                        System.out.println(Thread.currentThread().getId() + ": (a) ");

                        // Will execute in the originating thread
                        lock.delegate(() -> {
                                System.out.println(Thread.currentThread().getId() + ": (b) ");

                                sleep(2000);

                                System.out.println(Thread.currentThread().getId() + ": (c) ");
                        });

                        // This should execute first when the delegate has finished executed, so after 1 : (c) but since it a separate thread,
                        // ofcourse the originating thread might also execute prior, which happens because of our sleep here
                        sleep(2000);
                        System.out.println(Thread.currentThread().getId() + ": (d) ");

                }).start();

        });

        System.out.println(Thread.currentThread().getId() + ": (2)");

}

private static void sleep(long millis) {
        try {
                Thread.sleep(millis);
        } catch (InterruptedException e) {
                e.printStackTrace();
        }
}

      

Outputs:

1: (1)
13: (a) 
1: (b) 
1: (c) 
1: (2)
13: (d) 

      

Using an artist it would be something like this:

new JoinLock().await((lock) -> {
        Executors.newFixedThreadPool(1).submit(() -> {
                lock.delegate(runnable);
        });
});

      

The lock used in the example can be found here:

GitHub: Opensource repository . Have a look at: JoinLock.java which also uses: SimpleLock.java .

+1


source







All Articles