Does Spring @Transactional attribute work on a private method?
Asked Answered
I

8

273

If I have a @Transactional annotation on a private method in a Spring bean, does the annotation have any effect?

If the @Transactional annotation is on a public method, it works and opens a transaction.

public class Bean {
  public void doStuff() {
     doPrivateStuff();
  }
  @Transactional
  private void doPrivateStuff() {

  }
}

...

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();
Interpretation answered 9/12, 2010 at 8:38 Comment(0)
W
244

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 @Transactional) 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).

@See Spring Reference: Chapter 9.6 9.6 Proxying mechanisms

IMHO you should 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).

Widdershins answered 9/12, 2010 at 9:15 Comment(9)
Both points are not necessarily true. The first is incorrect - private methods can be invoked reflectively, but the proxy discovering logic chooses not to do so. The second point is only true for interface-based JDK proxies, but not for CGLIB subclass-based proxies.Blackleg
@skaffman: 1 - i make my statment more precise, 2. But the default Proxy is the Interface based - isn't it?Widdershins
That depends whether the target uses interfaces or not. If it doesn't, CGLIB is used.Blackleg
canu tell me the reson or some reference why cglib can not but aspectj can ?Noninterference
@phil: I added a link to the reference.Widdershins
Please don't shoot me but it is my opinion that if you are putting @Transactional on a private method, you don't know what you're doing. You shouldn' do that.Jaclynjaco
Reference from the link in answer block, if you want to use Spring Proxies [default environment], put annotation on doStuff() and call doPrivateStuff() using ((Bean) AopContext.currentProxy()).doPrivateStuff(); It will execute both methods in one same transaction if the propagation is reeuired [default environment].Coatee
@MichaelOuyang: thank you for pointing out this solution. But in the referenced Spring documentation, this approach is described this way: "The next approach is absolutely horrendous, and I am almost reticent to point it out precisely because it is so horrendous. You can (choke!) totally tie the logic within your class to Spring AOP by doing this"Widdershins
@Widdershins So, if I have added Transactional over a public method with a db save operation, but this public method is internally calling a private method which also has a db save operation, then both the db operations together will be considered as a trancaction right?( i.e one fails other will get rolled back)Unbounded
B
269

The answer your question is no - @Transactional will have no effect if used to annotate private methods. The proxy generator will ignore them.

This is documented in Spring Manual chapter 10.5.6:

Method visibility and @Transactional

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. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

Blackleg answered 9/12, 2010 at 8:45 Comment(3)
Are you sure about this? I would not expect it to make a difference.Ruddie
how about if the proxying style is Cglib?Humberto
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?Accouchement
W
244

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 @Transactional) 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).

@See Spring Reference: Chapter 9.6 9.6 Proxying mechanisms

IMHO you should 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).

Widdershins answered 9/12, 2010 at 9:15 Comment(9)
Both points are not necessarily true. The first is incorrect - private methods can be invoked reflectively, but the proxy discovering logic chooses not to do so. The second point is only true for interface-based JDK proxies, but not for CGLIB subclass-based proxies.Blackleg
@skaffman: 1 - i make my statment more precise, 2. But the default Proxy is the Interface based - isn't it?Widdershins
That depends whether the target uses interfaces or not. If it doesn't, CGLIB is used.Blackleg
canu tell me the reson or some reference why cglib can not but aspectj can ?Noninterference
@phil: I added a link to the reference.Widdershins
Please don't shoot me but it is my opinion that if you are putting @Transactional on a private method, you don't know what you're doing. You shouldn' do that.Jaclynjaco
Reference from the link in answer block, if you want to use Spring Proxies [default environment], put annotation on doStuff() and call doPrivateStuff() using ((Bean) AopContext.currentProxy()).doPrivateStuff(); It will execute both methods in one same transaction if the propagation is reeuired [default environment].Coatee
@MichaelOuyang: thank you for pointing out this solution. But in the referenced Spring documentation, this approach is described this way: "The next approach is absolutely horrendous, and I am almost reticent to point it out precisely because it is so horrendous. You can (choke!) totally tie the logic within your class to Spring AOP by doing this"Widdershins
@Widdershins So, if I have added Transactional over a public method with a db save operation, but this public method is internally calling a private method which also has a db save operation, then both the db operations together will be considered as a trancaction right?( i.e one fails other will get rolled back)Unbounded
I
37

By default the @Transactional attribute works only when calling an annotated method on a reference obtained from applicationContext.

public class Bean {
  public void doStuff() {
    doTransactionStuff();
  }
  @Transactional
  public void doTransactionStuff() {

  }
}

This will open a transaction:

Bean bean = (Bean)appContext.getBean("bean");
bean.doTransactionStuff();

This will not:

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();

Spring Reference: Using @Transactional

Note: 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!

Consider the use of AspectJ mode (see below) if you expect self-invocations to be wrapped with transactions as well. In this case, there won't be a proxy in the first place; instead, the target class will be 'weaved' (i.e. its byte code will be modified) in order to turn @Transactional into runtime behavior on any kind of method.

Interpretation answered 9/12, 2010 at 9:0 Comment(3)
Do you mean bean = new Bean();?Ruddie
Nope. If I create beans with new Bean(), the annotation will never work at least without using Aspect-J.Fireboard
thanks! This explains weird behaviour I was observing. Quite counter intuitive this internal method invocation restriction...Garris
T
33

If you need to wrap a private method inside a transaction and don't want to use AspectJ, you can use TransactionTemplate.

@Service
public class MyService {
    @Autowired
    private TransactionTemplate transactionTemplate;

    private void process() {
        transactionTemplate.executeWithoutResult(status -> processInTransaction());
    }

    private void processInTransaction(){
        //...
    }
}
Tetrahedron answered 25/10, 2018 at 13:0 Comment(3)
Good to show TransactionTemplate usage, but please call that second method ..RequiresTransaction rather than ..InTransaction. Always name stuff how you would like to read it a year later. Also I would argue to think if it really requires a second private method: Either put its content directly in the anonymous execute implementation or if that becomes messy it might be an indication to split of the implementation into another service that you then can annotate @Transactional.Pulmonic
@Stuck, the 2nd method is indeed not necessary, but it answers the original question which is how to apply a spring transaction on a private methodTetrahedron
yes, I already upvoted the answer but wanted to share some context and thoughts about how to apply it, because I think from an architecture standpoint this situation is a potential indication for a design flaw.Pulmonic
B
19

Yes, it is possible to use @Transactional on private methods, but as others have mentioned this won't work out of the box. You need to use AspectJ. It took me some time to figure out how to get it working. I will share my results.

I chose to use compile-time weaving instead of load-time weaving because I think it's an overall better option. Also, I'm using Java 8 so you may need to adjust some parameters.

First, add the dependency for aspectjrt.

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>

Then add the AspectJ plugin to do the actual bytecode weaving in Maven (this may not be a minimal example).

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.8</version>
    <configuration>
        <complianceLevel>1.8</complianceLevel>
        <source>1.8</source>
        <target>1.8</target>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Finally add this to your config class

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

Now you should be able to use @Transactional on private methods.

One caveat to this approach: You will need to configure your IDE to be aware of AspectJ otherwise if you run the app via Eclipse for example it may not work. Make sure you test against a direct Maven build as a sanity check.

Brame answered 28/2, 2016 at 20:21 Comment(2)
if the proxying method is cglib, there is no need to implements a interface of which the method should be public, then it is able to use @Transactional on private methods?Humberto
Yes, it works on private methods, and without interfaces! As long as AspectJ is configured properly, it basically guarantees working method decorators. And user536161 pointed out in his answer that it will even work on self-invocations. It's really cool, and just a tiny bit scary.Brame
K
6

Spring Docs explain that

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.

Consider the use of AspectJ mode (see mode attribute in table below) if you expect self-invocations to be wrapped with transactions as well. In this case, there will not be a proxy in the first place; instead, the target class will be weaved (that is, its byte code will be modified) in order to turn @Transactional into runtime behavior on any kind of method.

Another way is user BeanSelfAware

Karynkaryo answered 9/12, 2010 at 9:15 Comment(2)
could you add a reference to BeanSelfAware? It doesn't look like a Spring's classCalkins
@Calkins Suppose, it's about self injection (provide a bean with an instance of itself wrapped into a proxy). You can see examples in https://mcmap.net/q/110526/-spring-transaction-method-call-by-the-method-within-the-same-class-does-not-work/355438.Caves
T
4

The answer is no. Please see Spring Reference: Using @Transactional :

The @Transactional annotation may be placed before an interface definition, a method on an interface, a class definition, or a public method on a class

Teenager answered 9/12, 2010 at 8:48 Comment(0)
C
3

Same way as @loonis suggested to use TransactionTemplate one may use this helper component (Kotlin):

@Component
class TransactionalUtils {
    /**
     * Execute any [block] of code (even private methods)
     * as if it was effectively [Transactional]
     */
    @Transactional
    fun <R> executeAsTransactional(block: () -> R): R {
        return block()
    }
}

Usage:

@Service
class SomeService(private val transactionalUtils: TransactionalUtils) {

    fun foo() {
        transactionalUtils.executeAsTransactional { transactionalFoo() }
    }

    private fun transactionalFoo() {
        println("This method is executed within transaction")
    }
}

Don't know whether TransactionTemplate reuse existing transaction or not but this code definitely do.

Caves answered 5/5, 2019 at 21:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.