There are 'duplicate' questions regarding spring annotation 'inheritance', with resolute, but probably incorrect answer: NO(not inherited). Update: it's provably wrong.
Annotations aren't inherited when we override method, sure. But mere existence of
org.springframework.core.annotation.AnnotationUtils#findAnnotation
which follows method overrides begs the question if (and how) are @Transactional annotations "inherited". I can also find(through SpringTransactionAnnotationParser):
org.springframework.core.annotation.MergedAnnotations.SearchStrategy
which TYPE_HIERARCHY enum especially suggests that spring 'inherits' them.
Question:
So having in parent class method
@Transactional public void a() {}
and it child class method
@Overrides
public void a() {}
will there be opened transaction or not if I call a()? Lets assume, that proxy is correctly created, method is public, etc. etc. Everything else if fine.
is there some document/best practices how to configure this behavior, considering there are evidently multiple strategies for this.
does anyone know where are these rules documented, I'd like to know what happens say if parent is interface, or abstract method etc. and code doing the scanning isn't that easy not to be sure I'm not overlooking something.
EDIT 2: User dan1st tried code from 'edit 1' below and got different behavior for testTxInheritanceB. Unless I'm wrong somewhere/somehow, this proves that this annotation interpretation is really configurable (via SpringTransactionAnnotationParser or differently) and it's configured differently based on spring version (3.2.0<>2.6.6) or it's based on some autoconfigured property (my code base has LOT of dependencies, which potentially might alter behavior). I will (in a week) dissect project and compare it with fresh springboot project, trying to find reasoning behind it/mwo.
EDIT 1: it's very easy to actually test both cases; I'm asking here instead to get sources to wisdom to find why it actually works like this. The truth is, in default spring setting, when you're implementing abstract method it does not matter if you put @Transactional over abstract method or it's implementation. If you call overriding non-abstract method, in both cases TX will be opened. So that is the answer for my question 1, what will happen. Now we're missing why it's like that and what rules leads to this behavior, if there are some recommendations/implementation patterns when overriding such methods, and how we can configure strategies I mentioned, if/when and when/why not to do that.
For code below methods testTxInheritanceA and testTxInheritanceB yields: "Transaction active: true", method testTxInheritanceC obviously yields: Transaction active: false.
Sample RestController code:
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController()
@RequestMapping(path = {"/test"})
@Slf4j
@Validated
@AllArgsConstructor
public class TestController {
@Autowired
TestController.ParentNoChildYes_Child parentNoChildYesChild;
@Autowired
TestController.ParentYesChildNo_Child parentYesChildNoChild;
@Autowired
TestController.NotTranstactional notTranstactional;
@GetMapping(path = "/testTxInheritanceA", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
public String testTxInheritanceA() {
return "Transaction active: "+parentNoChildYesChild.test();
}
@GetMapping(path = "/testTxInheritanceB", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
public String testTxInheritanceB() {
return "Transaction active: "+parentYesChildNoChild.test();
}
@GetMapping(path = "/testTxInheritanceC", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
public String testTxInheritanceC() {
return "Transaction active: "+notTranstactional.test();
}
public static abstract class ParentYesChildNo_Parent {
@Transactional
public abstract boolean test();
}
@Service
public static class ParentYesChildNo_Child extends TestController.ParentYesChildNo_Parent {
@Override
public boolean test() {
return TransactionSynchronizationManager.isActualTransactionActive();
}
}
public static abstract class ParentNoChildYes_Parent {
public abstract boolean test();
}
@Service
public static class ParentNoChildYes_Child extends TestController.ParentNoChildYes_Parent {
@Override
@Transactional
public boolean test() {
return TransactionSynchronizationManager.isActualTransactionActive();
}
}
@Service
public static class NotTranstactional {
public boolean test() {
return TransactionSynchronizationManager.isActualTransactionActive();
}
}
}
Check the Javadoc of
@Transactional:So, if you have
then
someMethodis logically@Transactional(from Spring's perspective). This also applies ifsomeMethodis overwritten. It should also be mentioned that (as stated in the Javadoc)@Transactionalon a subclass does not affect methods declared only in superclasses.However, it is different for
In this case,
someMethodwill not be transactional unless you annotate the overridden version explicitly.So, for your questions:
No. As previously described, the overridden method won't be transactional. However, you can get around this by creating a wrapper around that method:
then
yourMethodImplwill be executed in a transaction ifyourMethodis called.As mentioned, you can check the Javadoc of
@Transactionalwhich mentioned it being inherited on class level but not when overriding methods. I don't know of any other strategies and I don't think you can customize theSearchStrategyof@Transactional.These things should do the same but I recommend you to try it yourself if you want to be sure.