31

In Spring, a method that is annotated with @Transactional will obtain a new transaction if there isn't one already, but I noticed that a transactional method does not obtain any transaction if it is called from a non-transactional one. Here's the code.

@Component
public class FooDao {
    private EntityManager entityManager;

    @PersistenceContext
    protected void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Transactional
    public Object save(Object bean) {
        return this.entityManager.merge(bean);
    }

    public Object saveWrap(Object bean) {
        return save(bean);
    }
}

@Component
public class FooService {
    private FooDao fooDao;

    public void save(Object bean) {
        this.fooDao.saveWrap(bean); // doesn't work.
        this.fooDao.save(bean); // works
    }
}

saveWrap() is a regular method that calls save() which is transactional, but saveWrap() won't persist any changes.

I'm using Spring 3 and Hibernate 3. What am I doing wrong here? Thanks.

Tom Tucker
  • 11,676
  • 22
  • 89
  • 130

4 Answers4

47

It is one of the limitations of Springs AOP. Because the dao bean is in fact a proxy when it is created by spring, it means that calling a method from within the same class will not call the advice (which is the transaction). The same goes for any other pointcut

Clinton Bosch
  • 2,497
  • 4
  • 32
  • 46
  • 4
    There is no harm in annotating the saveWrap method with @Transactional. The default behavior with transaction propagation is REQUIRED which means that if you were to get a nested transaction (ie. you are in a a transaction and then you call another method which is also @Transactional) would simply be to use the existing transaction and NOT create another one if that is what you are scared of) – Clinton Bosch Feb 24 '11 at 19:38
  • how about use cglib to proxy? I remember cglib proxy advice every method even you call it within the same class. – hiway Jan 29 '18 at 04:08
  • @ClintonBosch what if this new Transactional method (being called from another Transactional method with REQUIRED propagation) sets the propagation to REQUIRES_NEW ? I am actually looking at a codebase doing this very thing, and I am curious if it will still use the same transaction or create a new one. – Mr Matrix Jan 22 '20 at 23:58
15

Yes, this is expected behaviour. @Transactional tells spring to create a proxy around the object. The proxy intercepts calls to the object from other objects. The proxy does not intercept calls within the object.

If you want to make this work, add @Transactional on the method that is invoked from "outside".

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
  • 2
    Another option would be marking the whole class as transactional by placing @Transactional() (with possibly readOnly=true/false, propagation=something etc.) at the top of the class and then overriding the readOnly and/or propagation-values per-method as needed. – esaj Feb 24 '11 at 19:31
4

This is a bit late I know, but would just like to add a way to overcome this limitation is that within the method obtain the spring bean from the application context and invoke the method. When the spring bean is obtained from the application context it will be the proxy bean not the original bean . Since the proxy bean is now invoking the method instead of the original bean the transaction advice will be implemented on it.

prashant
  • 1,382
  • 1
  • 13
  • 19
1

A possible workaround is to call the method like if it was invoked from "outside"

You can do it by getting the current proxy of the component and then call the method :

 ((MyService) AopContext.currentProxy()).innerMethod();

Source: https://www.programmersought.com/article/58773839126/

bastien
  • 121
  • 4