3

We use Spring @Transactional annotations on business layer methods. When we moved to Java 8 these methods have been converted to use Async features of Java 8 and use completable Futures and chain several async calls to the data layer. E.g.

@Transactional
public CompletableFuture<Entity> updateEntity(ID id) {
   repository1.get(id)
      .thenComposeAsync(item -> repository1.save(), executor); 
}

The above code demonstrates that we want to chain multiple database layer async calls.

The @Transactional annotation seems to only support blocking calls at this time and all the transaction context information is kept in ThreadLocal. The above method creates 3 transactions. The outer transaction starts and completes as soon as the future is returned. repository1.get(id) runs in another transaction and repository1.save() runs in it's own transaction.

Is there a standard way to have multiple async calls run in a single transaction without having to rewrite the transaction interceptor. Also seems like we need to copy thread local variables in TransactionSynchronizationManager from one thread to the other to get this to execute in a single transaction.
Appreciate feedback from Spring team if they have plans to enhance spring-tx to support this use case.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
ShriN
  • 31
  • 1
  • 2
  • 2
    good question, but it is pretty known that spring transactional is bound to a single thread - I highly doubt that it could be possible to do what you want – Eugene Nov 18 '17 at 13:10

1 Answers1

0

I had a similar hurdle in a problem I was trying to solve.

In short:

  1. The easiest and most effective way to solve this is via the producer-consumer pattern (and pipe to a single transactional worker thread). See example below.
  2. If your problem is meniable to it, you could use batching
  3. You could solve this problem naturally with JTA, of which the transaction manager by definition deals with this problem (which, as you ask spring-tx has supported since 2003), which would give you likely more overhead (and complexity) than you bargained for
  4. Fiddle with the transaction interceptor and TransactionSynchronizationManager and solve via a hack-ey workaround which most TransactionManagers are not built to cope with

My transaction object in a single thread looked roughly like:

@Service
public class TransactionalObject {

    @Autowired
    private BlockingQueue<Runnable> queue;

    @Transactional
    public void consumeTransactionalRunnables() {
         try {
                while (!Thread.currentThread().isInterrupted()) {
                    Runnable execution = queue.take();
                    if (execution instanceof StopExecution) {
                        return;
                    }
                    execution.run();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }               
    }

}

This object was on its own thread, and the producer threads put onto the BlockingQueue queue. The poison pill in this code sample is the StopExecution Runnable type.

I found this solution to have minimal performance overhead (compared to other options), easily-manageable, easily-readable, and minimally invasive.

Dovmo
  • 8,121
  • 3
  • 30
  • 44
  • Does this mean all db calls in my application would execute on a single thread? If so, there would be a scalability issue, wouldn't it? – ShriN Nov 20 '17 at 19:07
  • Regarding option 4, is there a TransactionManager implementation that works by passing lightweight context between threads (custom executor) for Spring? – ShriN Nov 20 '17 at 19:10
  • If you were to go down this route, you would most likely have multiple transactional consumers (depending on how many transactions you want) for each transactional execution, and could easily create a factory of them. Even better, your idea to create a custom `Executor` would work great with `CompletableFuture`. You could use the Spring `TransactionTemplate` inside of your `execute`/`submit` method and instantiate it with the TM. – Dovmo Nov 20 '17 at 19:25