2

I try to get the TaskExecutor (ThreadPoolTaskExecutor) to work for a currently well working service with Transactions but when i start the Task the existing Transaction is not found anymore.

(When below start() Method is called there is already a transaction active) (The SimplePOJO ist out of context and just holds some data lets say to be stored in the DB)

@Component
@Transactional(rollbackFor = { Exception.class })
public class MyService {

    @Autowired
    MyTaskRunner myTaskRunner;

    public void start() {

        // TransactionSynchronizationManager.isActualTransactionActive() -> true

        SimplePOJO pojo = new SimplePOJO();
        myTaskRunner.executeTask(pojo);
    }
}

Above Service uses this Component:

@Component
public class MyTaskRunner implements ApplicationContextAware {

    @Autowired
    private TaskExecutor taskExecutor;

    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        MyTaskRunner.applicationContext = applicationContext;
    }

    public void executeTask(SimplePOJO simplePOJO) {

        // TransactionSynchronizationManager.isActualTransactionActive() -> true

        MyDelegate myDelegate = applicationContext.getBean(MyDelegate.class);
        myDelegate.setSimplePOJO(simplePOJO);
        taskExecutor.execute(myDelegate);
    }
}

That gets a ThreadPoolTaskExecutor configured this way:

<bean id="taskExecutor"
    class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="1" />
    <property name="maxPoolSize" value="5" />
    <property name="queueCapacity" value="25" />
</bean>

And should finally start the Async work in a new transaction per task:

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class MyDelegate implements Runnable {

    private SimplePOJO simplePOJO;

    @Override
    public void run() {

        // TransactionSynchronizationManager.isActualTransactionActive()); -> False!

        if (this.simplePOJO != null) {
            this.doStuff(angebotAnfrageContainer);
        }
    }

    public void setSimplePOJO(SimplePOJO simplePOJO) {
        this.simplePOJO = simplePOJO;
    }

    @Async
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    private void doStuff() {

        // TransactionSynchronizationManager.isActualTransactionActive()); -> False!

        // do stuff with the pojos data in the transaction (persist on DB)
    }

}

Do you have any suggestions what i am missing here?

Very many thanks in advance!

What i tried Wrote above code & searched the web for hours, this is what i found out:

I have to admit i am very new to Spring and havent yet understood the framework too well. Within the last few hours of trying and searching the web i think it has something to do with Proxys beeing created from spring and that chain breaks when i call the execute()-Method or when the TaskRunner calls the run()-Method that instead of calling the Proxy calls the Implementation (MyDelegate) directly ... but i have absolutly no idear how i could verify or change that.

Very many thanks in advance

JBA
  • 2,769
  • 5
  • 24
  • 40
  • Spring uses proxies and proxies will only work for external method calls. Your run method calls the method with the `@Transactional` however this is always from within the proxy, so no transaction is ever going to be started. – M. Deinum Apr 30 '14 at 12:58
  • Very many thanks for your reply. I think i understood that... what would you recomend? (I will now try to have the doStuff()-method in another Spring-Bean and call this from the run()-Method) – JBA Apr 30 '14 at 13:10
  • O.k. you input was the key and i could solve it by having above doSomething()-Method in a own Component which then gets called from the run()-Method. After all this trying and stuff i need to rethink if i even need the @Async anymore ;) – JBA Apr 30 '14 at 13:20
  • Dont hesitate to make it an anwer so i can accept & upvote it :) Just simple stuff may be absolutly clear for ppl with Spring knowledge but not Junior Developpers that just startet it like i am – JBA Apr 30 '14 at 13:24
  • Why not simply put it on the `run` method why add additional complexity. Also you don't need the `@Async` because you are scheduling it yourself. – M. Deinum Apr 30 '14 at 13:46

1 Answers1

3

Transactions are applied using AOP. Spring, by default, uses proxies to apply AOP. The usage of proxies result in only calls into (external method calls) the object are intercepted. In your case you are doing a method call from inside the object which will not pass through the proxy and will not be intercepted, basically rendering your @Transactional useless.

Simply moving the @Transactional to the run method should fix your problem as the run method will now run in a transaction.

@Transactional
public void run() {...}

You can also remove the @Async as that doesn't do anything here, you are executing task with a TaskExecutor already making them async (depending on which TaskExecutor you use).

Links:

  1. Understanding AOP Proxies
  2. TaskExecutor
M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • If i do the @Transactional on the run()-method i get a BeanNotOfRequiredTypeException: Bean named 'myDelegate' must be of type [MyDelegate], but was actually of type [$Proxy86] – JBA Apr 30 '14 at 15:14
  • 1
    You are right you should use classbased proxies instead of interface based proxies if you want that. Spring by default uses interface based proxies. – M. Deinum May 01 '14 at 05:07