13

I have a question: Why when we annotate method with @Scheduled and @Transaction, transaction doesn't work? I know that the @Scheduled call my class instead of proxy class that created by Spring, but can't understand this behavior.

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserServiceImpl implements UserService {

    @Override
    @Scheduled(fixedRateString = "${somestring}",initialDelayString = "${anotherstring}")
    @Transactional
    public void doSomething() {

        }
    }

I have two solutions of this problem:

  1. Call proxy from Scheduled method.

  2. Implement ConcurrentTaskScheduler and replace object of ScheduledMethodRunnable(that is with my class) with object of ScheduledMethodRunnable with proxy.

But this solutions is very inconvenient.

Can you explaim me why @Scheduled works like this?

Thank you!

Rana_S
  • 1,520
  • 2
  • 23
  • 39
Artiom Saidanov
  • 286
  • 1
  • 3
  • 13
  • What do you mean by _doesn't work_? – Sotirios Delimanolis Jul 27 '17 at 15:55
  • I mean that there is no transaction in method, that annotated with @Transactional, if scheduler calls this method.Because scheduler calls method of my class instead of method of proxy class. – Artiom Saidanov Jul 27 '17 at 16:07
  • It might work when you scrap the `implements UserService` part because that should trigger a different type of proxy: https://stackoverflow.com/a/30489513/995891 – zapl Jul 27 '17 at 16:25

2 Answers2

5

It happens because to process both annotations MAGIC is used.

I suppose there are several things happens:

  1. UserServiceImpl is created.
  2. @Scheduled annotation is processed and reference to bean is stored to invoke it at appropriate time.
  3. @Transactional annotation is processed. It create proxy which store reference to original bean. Original bean is replaced to proxy in application context.

If step 2 and 3 passed in different order then you had no problem.

I don't know how to control order in which annotation is processed. I don't even sure it is possible at all.

There is basically two solution.

  1. Use different kind of magic to process @Transaction. Default way is to create proxy object, but it is possible to instruct Spring to instrument current class.
  2. Split this to two class each of them will have method with only one annotation.

Example:

@Service
public class UserServiceImpl implements UserService {

    @Override
    @Transactional
    public void doSomething() {

    }
}

@Service
public class UserServiceScheduler {

    @Inject
    private UserService service;

    @Scheduled(fixedRateString = "${somestring}",initialDelayString = "${anotherstring}")
    public void doSomething() {
         service.doSomething();
    }
}

I'm personally recommend second approach.

talex
  • 17,973
  • 3
  • 29
  • 66
  • I know that solution, and it works. But I also want to know why Spring has such behavior? – Artiom Saidanov Jul 27 '17 at 16:23
  • @АртёмСайданов I added some explanation. More detail on how `@Transactional` works you can easily find in internet. – talex Jul 27 '17 at 16:30
  • @talex, My method is annotated both with `@Scheduled` and `@Transactional` and I can see that the scheduler is working. I use a cron format here like this: `@Override @Scheduled(cron = "0 * * * * ?")` . Now I am a little bit of confused that it shouldn't be working but it works! Any explanation would be nice. Thanks – user404 Aug 07 '22 at 05:12
0

The Question is not private or public, the question is: How is it invoked and which AOP implementation you use!

If you use (default) Spring Proxy AOP, then all AOP functionality provided by Spring (like @Transational) will only be taken into account if the call goes through the proxy. -- This is normally the case if the annotated method is invoked from another bean.

This has two implications:

  • Because private methods must not be invoked from another bean (the exception is reflection), their @Transactional Annotation is not taken into account.
  • If the method is public, but it is invoked from the same bean, it will not be taken into account either (this statement is only correct if (default) Spring Proxy AOP is used).

you can also use the aspectJ mode, instead of the Spring Proxies, that will overcome the problem. And the AspectJ Transactional Aspects are woven even into private methods (checked for Spring 3.0).

refer: http://docs.spring.io/spring/docs/3.2.4.RELEASE/spring-framework-reference/html/aop.html#aop-proxying

ankit
  • 2,591
  • 2
  • 29
  • 54