Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

Parent/child thread transactions with Spring Data

119 views
Skip to first unread message

Michael Congdon

unread,
Jul 30, 2024, 9:08:29 PM7/30/24
to narayana-users
I need to perform the following functionality and have not found the right solution with Narayana, even though I believe it supports it.

spring-boot-starter-parent-3.3.0
spring-boot-starter-data-jpa-3.3.0
hibernate-core-6.5.2
narayana-jta-7.0.1.Final

1) In my @Service, start a parent/coordinating transaction with @Transactional annotation on a method.

2) That service would launch 2 separate threads, via other @Autowired services that have @Transactional-annotated methods.

3) Our custom thread-local variables get written to those child transactions' threads respectively.  These are required for some custom runtime EntityManagerFactory and DataSource functionality we've implemented (writing to 2 different XADataSources dynamically chosen at runtime based on those thread-local values).

4) Run the child transactions but ensure the parent transaction controls the final commit/rollback.

I've tried things like @Async, CompletableFuture and running our transactions via ExecutorService (to run our @Autowired services), but the child threads have no awareness of the parent thread and its "coordinating" transaction.

I'd like to use Spring @Transactional annotations if possible.  How do I use Narayana to coordinate 2 child threads under a parent thread?

Michael Musgrove

unread,
Aug 5, 2024, 12:21:46 PM8/5/24
to narayana-users
Your intuition is correct although it isn't supported directly in Narayana. Integrations, such as WildFly and Quarkus, support transaction propagation via the SmallRye implementation [1] of MicroProfile context propagation [2]. The SmallRye project validates transaction context propagation using Narayana in its test suite so it is compatible. I guess it would be helpful for someone to provide a quickstart that shows how to configure narayana to use it. You could either investigate doing that yourself (by looking at how SmallRye or WildFly or Quarkus do it) or you could create an issue in our issue tracker [3] and see if anyone picks it up and runs with it - it is certainly a quickstart that I'd like to see in Narayana.

[1] https://github.com/smallrye/smallrye-context-propagation
[2] https://download.eclipse.org/microprofile/microprofile-context-propagation-1.0/microprofile-context-propagation.html#txcontext
[3] https://issues.redhat.com/projects/JBTM/summary

Michael Musgrove

unread,
Aug 12, 2024, 12:42:15 PM8/12/24
to narayana-users
I added an issue tracker.

Michael Congdon

unread,
Oct 2, 2024, 4:18:03 PM10/2/24
to narayana-users
Your solution worked.  I implemented SmallRye Context Propagation and it successfully propagated the JTA Transaction from the parent thread to all child threads launched in the SmallRyeManagedExecutor.

In a nutshell, the code looks like this:

@Transactional
public void myMethod() {
  // Create 2-thread executor that only propagates the transaction to child threads
  SmallRyeManagedExecutor executor = SmallRyeManagedExecutor.builder()
.propagated(ThreadContext.TRANSACTION)
.cleared(ThreadContext.ALL_REMAINING)
.maxAsync(2).injectionPointName("MyClass.myMethod").build();

  //Create and contextualize the Runnables for the child threads
  Runnable wrapped = executor.getThreadContext()
.contextualRunnable(() -> myBean.doSomething());
  Runnable wrapped2 = executor.getThreadContext()
.contextualRunnable(() -> myBean.doSomethingElse());
  
  // Run the runnables
  Future<?> future = executor.submit(wrapped);
  Future<?> future2 = executor.submit(wrapped2);
  future.get();
  future2.get();
  
  // Shut down the executor's thread pool
  executor.shutdown();

  // When this @Transactional-annotated method finishes, Spring will manage the commit or rollback if this method and the actions in the child threads.
}


The key is in the executor.getThreadContext().contextualRunnable() method.  The SmallRyeTheradContext propagates everything we tell it to (in this case. ThreadContext.TRANSACTION) over to the threads it uses to execute the Runnables.  Other values could be ThreadContext.APPLICATION, ThreadContext.CDI and more.  You can even create your own context provider to propagate your own custom objects.  But ThreadContext.TRANSACTION covers the Spring-managed jakarta.transaction.Transaction that Narayana is using, so no custom context provider was needed.

NOTE: executor.propagated(...) defines what to include;  executor.cleared(...) defines what to explicitly NOT propagate to the child threads.

Thank you for your suggestion.  This was the final piece I needed for a complex year-long challenge.

Michael

Michael Musgrove

unread,
Oct 3, 2024, 4:21:34 AM10/3/24
to narayana-users
Very nice work, we should use your investigation and work as the basis for the related narayana quickstart issue tracker, thanks.
Reply all
Reply to author
Forward
0 new messages