Since the "correct" answer is already here but has little to no explanation I am going to add the same answer but with explanation.
First of all you have to understand how most Spring annotations work. When spring creates a bean with a @transactional annotation to Autowire it (or in the case of the controller simply to expose the endpoint) what really happens is that Spring creates a proxy around your class. Thus spring is able to add behavior before or after method calls.
E.g. you have an @Service class called DummyService
you actually don't get a DummyService instance when doing this:
@Autowired
private DummyService dummyService;
The actual instance is a Proxy around your DummyService.
Now inside your DummyService you do not (and cannot) see that the instance is actually a Proxy. This means if you call another method inside DummyService you not calling the Proxy but rather the actual instance and you don't get the additional behavior of the proxy, e.g. you have no Transaction.
So let's look at a few examples:
First our service:
@Service
public class DummyService {
public void doStuff() {
// do stuff
transaction() //call to DummyService itself. No transaction!
// do more stuff
}
@Transactional
public void transaction() {
// do stuff
}
}
now a Controller
@Service
public class DummyController {
@Autowired
private DummyService dummyService; // Will hold the proxy instance
@GetMapping(path = "/stuff")
public void doStuff() {
// do stuff
dummyService.transaction() //call to the autowired proxy. We got a transaction!
transaction() //again no proxy = no transaction!
// do more stuff
}
@GetMapping(path = "/transaction") //Calls to this url get handled by the proxy instance = transaction!
@Transactional
public void transaction() {
// do stuff
}
@GetMapping(path = "/stuff-without-transaction")
public void doStuffWithoutTransaction() {
DummyService dummy2 = new DummyService(); //Creating the service yourself means no proxy
// do stuff
dummy2.transaction() //no proxy = no transaction!
// do more stuff
}
}
So as others said: simple answer is to move the @Transactional
annotation to a different class, autowire the instance, get the proxy and thus get a transaction. You have to make calls through the Spring proxy instances to make this additional behavior work.
Rule of the thumb: never call @Transactional
Methods from inside your class to avoid this problem! And never create Instances of classes with @Transactional
methods yourself.