Spring @Scheduled with internal transaction triggering Propagation error

Asked

Viewed 580 times

2

I am trying to run a service that is based on running by cron with annotation @Scheduled, but every time a database transaction needs to be opened within the annotated method with @Scheduled receive the error reported below:

@Service
public class Tasks{

    @Autowired
    private OpenServiceRepository openServiceRepository;

    /*
     * Metodo que fica responsavel por verificar todos os faturamentos no qual
     * a data da venda seja a data atual menos 395 dias com os items sendo de
     * credito e nao gerando debito
     * 
     */
    @Scheduled(cron="*/5 * * * * ?")
    @Transactional(propagation = Propagation.MANDATORY)
    public void demoServiceMethod(){
        long count = openServiceRepository.count();
        System.out.println("O numero de Serviços em aberto é: " + count);
    }
}

Below follows the full stacktrace:

 15:34:25.005 [pool-1-thread-1] ERROR o.s.s.s.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task.
    org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:359) ~[spring-tx-4.0.5.RELEASE.jar:4.0.5.RELEASE]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:420) ~[spring-tx-4.0.5.RELEASE.jar:4.0.5.RELEASE]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:257) ~[spring-tx-4.0.5.RELEASE.jar:4.0.5.RELEASE]
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95) ~[spring-tx-4.0.5.RELEASE.jar:4.0.5.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.0.5.RELEASE.jar:4.0.5.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644) ~[spring-aop-4.0.5.RELEASE.jar:4.0.5.RELEASE]
        at br.com.joocebox.quartz.Tasks$$EnhancerBySpringCGLIB$$d0429f10.demoServiceMethod(<generated>) ~[spring-core-4.0.5.RELEASE.jar:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.7.0_80]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[na:1.7.0_80]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.7.0_80]
        at java.lang.reflect.Method.invoke(Method.java:606) ~[na:1.7.0_80]
        at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65) ~[spring-context-4.0.5.RELEASE.jar:4.0.5.RELEASE]
        at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) ~[spring-context-4.0.5.RELEASE.jar:4.0.5.RELEASE]
        at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81) [spring-context-4.0.5.RELEASE.jar:4.0.5.RELEASE]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [na:1.7.0_80]
        at java.util.concurrent.FutureTask.run(FutureTask.java:262) [na:1.7.0_80]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178) [na:1.7.0_80]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292) [na:1.7.0_80]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_80]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_80]
        at java.lang.Thread.run(Thread.java:745) [na:1.7.0_80]

1 answer

1

The problem is that the implementation of @Transactional by default uses a proxy. When you write down the same method with an annotation like @Scheduled she doesn’t pass any proxy and so Spring has no way to intercept calls to control transactions.

Solution

Move the demoService to another class keeping the annotation Transacional at that second level:

@Service
public class DemoService {
    @Autowired
    private OpenServiceRepository openServiceRepository;

    @Transactional(propagation = Propagation.REQUIRED)
    public void demoServiceMethod(){
        long count = openServiceRepository.count();
        System.out.println("O numero de Serviços em aberto é: " + count);
    }
}

And use your new class within the service of Scheduling:

@Service
public class Tasks {   
    @Autowired
    private DemoService demoService;

    @Scheduled(cron="*/5 * * * * ?")
    public void fireDemoService(){
        demoService.demoServiceMethod();
        System.out.println("O numero de Serviços em aberto é: " + count);
    }
}

Thus the task will pass through the proxy in the call for demoServiceMethod and the transaction will open as expected.

Additionally, if you really need transactions within the same class, one option is to enable Weaving at compilation time or loading (e.g., using: @EnableTransactionManagement(mode = AdviceMode.ASPECTJ) and @Enableloadtimeweaving)


References:

Browser other questions tagged

You are not signed in. Login or sign up in order to post.