Search code examples
javatransactionsejb

Open new transaction when already inside ejb


Consider the following situation:

@Stateless
@Clustered
public class FacadeBean implements Facade {

    @EJB
    private Facade facade;

    @Override
    public void foo(List<Integer> ids) {
        // read specific id's from the db
        for (Integer id : ids) {
            facade.bar(id);
        }
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void bar(Integer id) {
        // save something to the db
    }

}

Method foo gets called from outside the ejb. I want each id to be executed in its own transaction, so that data get's persisted directly to the database. It is not possible to have the foreach outside of this class. I am wondering what is the best way to do this? At the moment I am injecting the interface again, to step over the ejb boundaries (so that the TransactionAttribute get's evaluated).


Solution

  • Your approach as to circular reference is perfectly fine. Circular reference in EJBs is allowed. This is even mandatory in order to start out a new transaction, or an @Asynchronous thread (otherwise the current thread would still block).

    @Stateless
    public class SomeService {
    
        @EJB
        private SomeService self; // Self-reference is perfectly fine.
    
    
        // Below example starts a new transaction for each item.
    
        public void foo(Iterable<Long> ids) {
            for (Long id : ids) {
                self.fooInNewTransaction(id);
            }
        }
    
        @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
        public void fooInNewTransaction(Long id) {
            // ...
        }
    
    
        // Below example fires an async thread in new transaction.
    
        @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
        public void bar(Iterable<Long> ids) {
            for (Long id : ids) {
                self.fooAsynchronously(id);
            }
        }
    
        @Asynchronous
        public void fooAsynchronously(Long id) {
            // ...
        }
    
    }
    

    Only in older containers, this did not work, most notably JBoss AS 5 with the ancient EJB 3.0 API. That's why people invented workarounds like SessionContext#getBusinessObject() or even manually grabbing via JNDI.

    Those are unnecessary these days. Those are workarounds not solutions.

    I'd personally only do it the other way round as to transactions. The foo() method is clearly never intented to be transactional.

    @Stateless
    public class SomeService {
    
        @EJB
        private SomeService self;
    
        @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
        public void foo(Iterable<Long> ids) {
            for (Long id : ids) {
                self.foo(id);
            }
        }
    
        public void foo(Long id) {
            // ...
        }
    
    }
    

    Depending on the concrete functional requirement, you could even make the foo(Long id) @Asynchronous, hereby speeding up the task.