Indirect Hibernate/JPA method call loses transaction
I'm using Spring/JPA2/hibernate with this code:
class A {
@Autowired
B b;
@RequestMapping("/test")
public void test(final HttpServletRequest r1, HttpServletResponse r2) throws ... {
b.inner(); // Works
b.outer(); // javax.persistence.TransactionRequiredException:
// no transaction is in progress ... :|
}
@Component
class B {
@PersistenceContext
EntityManager em;
public void outer() { inner(); 开发者_StackOverflow中文版}
@Transactional
public void inner() { em.flush(); }
}
Why does inner()
only when called indirectly loose the transaction?
http://static.springsource.org/spring/docs/current/reference/transaction.html#transaction-declarative-annotations
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.
The @Autowired
reference B b
(inside class A
), is wrapped with a Spring AOP transaction-aware proxy.
When b.inner()
is called, you are invoking it on the transaction-aware instance, and it is marked as @Transactional
. Thus a Spring managed transaction is started.
When b.outer()
is called, it is also on a transaction-aware instance, but it is not @Transactional
. Thus a Spring managed transaction is not started.
Once you are inside the invocation of outer()
the call to inner()
is not going through the transaction-aware proxy, it is being invoked directly. It is the same as this.inner()
. Since you are invoking it directly, and not through the proxy, it does not have the Spring transaction-aware semantics.
Since no transaction has been started, this results in the TransactionRequiredException
.
Possible solutions include making the method outer()
@Transactional
.
@Transactional
public void outer() { inner(); }
Or making the entire class @Transactional
.
@Transactional
@Component
class B {
@PersistenceContext
EntityManager em;
The transactional context lasts over the scope of your spring bean's entire lifespan. @Transactional notation has a scope of the entire component and you should annotate your @Component as @Transactional eg
@Transactional
@Component
class B {
@PersistenceContext
EntityManager em;
public inner() { }
public outer() { }
}
The methods inner and outer should accomplish individual units of work. If you need some helper function or what have you that is fine, but the unit of work that requires the transactional boundary should be scoped to each method. See the spring docs on @Transactional http://static.springsource.org/spring/docs/3.0.x/reference/transaction.html
精彩评论