I'm recently playing a little with Spring/JPA2 to understand better how it works. During my experiments I found some strange behaviour. The question is:
Why the following code works well (confirmed record added in db):
@Repository
public class UserDAO {
@PersistenceContext
EntityManager em;
@Transactional
public void add(User user) {
doAdd(user);
}
public void doAdd(User user) {
em.persist(user);
}
}
But the following (@Transactional annotation moved to inner method):
@Repository
public class UserDAO {
@PersistenceContext
EntityManager em;
public void add(User user) {
doAdd(user);
}
@Transactional
public void doAdd(User user) {
em.persist(user);
}
}
Throws exception:
javax.persistence.TransactionRequiredException: No transactional EntityManager available
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:273)
at com.sun.proxy.$Proxy556.persist(Unknown Source)
at com.example.UserDAO.doAdd(UserDAO.java:24)
...
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.
The @Transactional annotation support works by wrapping the actual DAO instance in a Proxy, which intercepts the method calls and starts/commits the transaction. In the second example the actual UserDAO instance is calling the doSave method and therefore there is no Proxy to intercept the method call.