If a (public) method is declared at a superclass of an @Transactional
target class, the method won't be wrapped in a transaction.
The problem lies in AbstractFallbackTransactionAttributeSource
:
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// Don't allow non-public methods, as configured.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
// -> This will resolve to the method at the superclass. This method has no @Transactional.
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
// First try is the method in the target class.
// -> This will resolve to the method at the superclass too because the method has not been overwritten.
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
}
// Second try is the transaction attribute on the target class.
// -> This will resolve to the superclass. The superclass has no @Transactional.
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
// Bug: This third case is missing:
// -> The target class has @Transactional.
// Maybe this can be combined with the case above.
if (targetClass != null) {
txAttr = findTransactionAttribute(targetClass);
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
if (specificMethod != method) {
// Fallback is to look at the original method.
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
// Last fallback is the class of the original method.
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
return null;
}
Comment From: jhoeller
This is by design: A class-level @Transactional
marker is meant to apply as a default to all methods of the declaring class and its subclasses. It does not apply to ancestor classes up the class hierarchy; inherited methods need to be locally redeclared in order to participate in a subclass-level annotation. See the @Transactional javadoc as well as https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html#transaction-declarative-annotations-method-visibility