14

From Does Spring @Transactional attribute work on a private method?

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings.

I can think of good reasons to exclude private and package-private methods, but why won't protected methods behave transactionally? The following stacktrace displays the correct behaviour for a public method (called through an interface proxy):

at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_51]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) ~[spring-tx-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281) ~[spring-tx-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at com.sun.proxy.$Proxy145.improveType(Unknown Source) ~[na:na]

When calling an "identical" protected method (through a non-interface CGLIB proxy), we get the following:

at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_51]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at my.company.webservices.facade.EntityFacade$$EnhancerBySpringCGLIB$$fd77735b.findEntity(<generated>) ~[spring-core-4.2.1.RELEASE.jar:na]

This is apparently a design decision (why?), but I consider it rather questionable that it fails silently, when it's clearly a developer error.

Edit This is obviously not an issue when using interfaces (only public methods in interfaces), but as Spring doesn't necessarily need an interface to proxy an object through CGLIB, calling a protected @Transactional method will behave just like a public method (i.e. called through a proxy), except that by design it ignores the transactionality.

Community
  • 1
  • 1
Kayaman
  • 72,141
  • 5
  • 83
  • 121
  • Theoretically, I suppose you could still use proxy AOP with package methods if you were calling between classes in-package. The primary issue is self-calls. You might consider filing an issue on this at the Spring JIRA. – chrylis -cautiouslyoptimistic- Dec 10 '15 at 10:01
  • 1
    Positive again. And thanks for edit up-edit on my answer ... – GhostCat Aug 24 '18 at 12:35
  • I used the following regex `@Transactional([^{](?!public))+ \{` to find possible annotations that won't have any effect (because they are on private, protected, package-private Methods) in our codebase. Doesn't find "self-reference-without-proxy"-calls to public methods of course - is there a plugin or something to spot those? – icyerasor Aug 24 '21 at 11:23

3 Answers3

26

Because of this:

In proxy mode (which is the default), only 'external' method calls coming in through the proxy will be intercepted. This means that 'self-invocation', i.e. a method within the target object calling some other method of the target object, won't lead to an actual transaction at runtime even if the invoked method is marked with @Transactional!

And this:

Due to the proxy-based nature of Spring's AOP framework, protected methods are by definition not intercepted, neither for JDK proxies (where this isn't applicable) nor for CGLIB proxies (where this is technically possible but not recommendable for AOP purposes). As a consequence, any given pointcut will be matched against public methods only!

The Spring guys would probably want to keep consistency with JDK proxies. You wouldn't want to have different proxy configuration and different results based on JDK versus CGLIB.

Lakatos Gyula
  • 3,949
  • 7
  • 35
  • 56
  • 2
    The self-invocation case is clear, but the second quote probably is the root issue. However it would be nice if it failed "non-silently", since `@Transactional` on a `protected` method would always be a bug. – Kayaman Dec 10 '15 at 10:26
  • 3
    _Why_ is clear: consistency with JDK proxies. If _failing silently_ is a good decision is debatable. – atamanroman Dec 10 '15 at 10:36
6

Additional info to the other answers.

Here is an example picture from the Spring blog: enter image description here

As you can see the proxy is wrapped around the implementing class (here the AccountServiceImpl) and the proxy itself implements only methods from the AccountService interface. An Interface provides only public methods (public contract), so a proxy can not be wrapped around the protected method and provide the transactional behavior, which it provides on public methods.

Calling methods in the same service is possible, if you would use AspectJ, but I'm not sure if this also counts for protected methods, because I haven`t used it until now.

kamwo
  • 1,980
  • 1
  • 23
  • 32
  • 1
    Sorry, I didn't mention that in this case there is no interface. The CGLIB proxy will be created as a subclass, so the `protected` method is accessible normally. – Kayaman Dec 10 '15 at 10:23
1

Protected methods are not part of the public contract. That's why they are not proxied. They are not visible to the consumer in most cases.

You could call protected methods if you a) wire by IF and cast down to the concrete implementation (bad) or b) wire the concrete implementation and the consumer resides in the same package. As only b) makes sense, I can see why Spring does not proxy protected methods. It's a rare corner case and would only work with CGLIB, not JDK proxies.

Maybe you wonder about the use case of extending a bean and calling super.myProtectedMethod(): those calls are not proxied at all, independent of access level.

atamanroman
  • 11,607
  • 7
  • 57
  • 81