您的位置:首页 > 产品设计 > UI/UE

Transaction visibility / proxy / propagation REQUIRED and REQUIRES_NEW

2016-11-12 14:26 405 查看


1. abstraction

After my post Spring Transaction propagation / readonly
on the propagation and readonly attribute, I would write some words concerning the propagation and the visibility of annoted method because I was victim to not having read up on how Spring proxies work, the ones responsible for annotation-based transaction
support.

So I will expose some problem via examples of how Spring proxies work, the propagation REQUIRED and REQUIRES_NEW and the visibility of annotated methods, then, the possible ways to resolve the encountered problem.


2. test

Step1:

We have the class MyClass:

@Transactional(propagation = Propagation.REQUIRED)
public class MyClass implementes MyInterface { ...

@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void myMethod1(String[] params){
//...
myMethod2(params);
//...
}
//...
}
with the interface MyInterface:

public MyInterface {
public void myMethod1(String[] params);
}
Now let’s say in myMethod1 we call a private method
myMethod2 in MyClass:
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
private void myMethod2(String[] params){
//...
}
When, we execute the method
myMethod1, it seems that Spring “ignores” the REQUIRES_NEW annotation and doesn’t start a new transaction for the second method
myMethod2.
From the Spring reference:
When using proxies, the @Transactional annotation should only be applied to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error will be raised, but the annotated method
will not exhibit the configured transactional settings.

So Spring ignores @Transactional annotation on non-public methods and Spring AOP operates on the interface level (MyInterface) and doesn’t intercept any calls to MyClass methods.

step2:

We will modify the visibility of the method
myMethod2
from private to public:

@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void myMethod2(String[] params){
//...
}
with the interface
MyInterface
:
public MyInterface {
public void myMethod1(String[] params);
public void myMethod2(String[] params);
}
When, we execute the method
myMethod1, it seems that Spring “ignores” again the REQUIRES_NEW annotation and doesn’t start a new transaction for the second method
myMethod2.
From the Spring reference:
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.

Even if we modify the second method myMethod2 to
public, calling it from within the method myMethod1 of same class will not start a new transaction.


3. solution

So, Spring turns out the second method myMethod2 REQUIRES_NEW annotation and ignores when the method is called from within the same class. How are there any way to start a new transaction within the first method
myMethod1 transaction? Is the only way to call another Spring managed bean that has transactions configured as REQUIRES_NEW?

solution 1:

The first complex solution is to use aspectj mode in transaction settings so that the transaction related code is weaved in the class and no proxy is created at runtime:

<tx:annotation-driven transaction-manager="txManager" mode="aspectj"/>
From the Spring reference:
Consider the use of AspectJ mode, 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.
The default mode “proxy” will process annotated beans to be proxied using Spring’s AOP framework (following proxy semantics, as discussed above, applying to method calls coming in through the proxy only). The alternative
mode “aspectj” will instead weave the affected classes with Spring’s AspectJ transaction aspect (modifying the target class byte code in order to apply to any kind of method call). AspectJ weaving requires spring-aspects.jar on the classpath as well as load-time
weaving (or compile-time weaving) enabled.

solution2:

As Spring creates a proxy class for the class which has annotated methods, a simple solution is to move the second method
myMethod2() to a separate class and the problem disappeared.

More, any annotation on the called method would be ignored (since the call happens on ‘this’ rather than on the Proxy) when one method in the proxied class calls another method in the same proxied class, Spring is not able to
handle this scenario implicitly.

solution3:

So, as any annotation on the called method would be ignored when one method in the proxied class calls another method in the same proxied class, another possible way of doing this is fetching the Spring proxy of the class, in
the class itself and call methods on it rather than this:

@Transactional(propagation = Propagation.REQUIRED)
public class MyClass implementes MyInterface { ...

@Autowired
private ApplicationContext applicationContext;

private MyInterface getSpringProxy() {
return applicationContext.getBean(this.getClass());
}

@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void myMethod1(String[] params){
//...
MyInterface myself = getSpringProxy();
//...
myself.myMethod2(params);
//...
}
//...
}
solution4 :
Another possible way of doing this it the
programmatically start a new transaction using TransactionTemplate:

@Transactional(propagation = Propagation.REQUIRED)
public class MyClass implementes MyInterface { ...

@Autowired
private ApplicationContext applicationContext;

@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void myMethod1(final String[] params){
//...
PlatformTransactionManager transactionManager = (PlatformTransactionManager)
applicationContext.getBean("transactionManager");
TransactionTemplate transactionTemplate = new TransactionTemplate(
transactionManager);
transactionTemplate.setPropagationBehavior(
TransactionDefinition.PROPAGATION_REQUIRES_NEW);
//
transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
// ...
myMethod2(params);
// ...
}
});
}
//...
}
Note: The method doInTransaction can accessed only to
final (constant) objects/variables.

In this post, we have exposed some problem via examples of how Spring proxies work, the propagation REQUIRED and REQUIRES_NEW and the visibility of annotated methods, then, the possible ways to resolve the encountered problem.

note:

I was trapped into the same problem after researching into spring transaction declarative mode and was searching into the net to see exactly how spring transaction works and this is the place where my search ends.

I must say, spring source should provide examples like this to help users understand the spring transaction because this is very basic and without knowing this, users will face problem implementing it.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: