Spring @Scheduled with internal transaction triggering propagation error

2

I'm trying to run a service that relies on running cron with the @Scheduled annotation, but every time a database transaction needs to be opened within the method annotated with @Scheduled I get the error reported below: / p>

@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 is the complete 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]
    
asked by anonymous 15.09.2015 / 20:38

1 answer

1

The problem is that the implementation of @Transactional by default uses a proxy . When you write the same method with an annotation as @Scheduled , it does not pass any proxy and so Spring has no way to intercept calls to handle transactions.

Solution

Move demoService to another class by keeping note Transacional on this 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 scheduling service:

@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);
    }
}

In this way the task will go through the proxy in the demoServiceMethod call and the transaction will open as expected.

In addition, if you really need transactions within the same class, one option is to enable weaving at compile time or loading (eg, using: @EnableTransactionManagement(mode = AdviceMode.ASPECTJ) and @EnableLoadTimeWeaving )

References:

21.11.2015 / 15:54