简体   繁体   中英

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).

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.

Do you really have to have both methods in one class? You can move bar() to an own bean and make it transactional. Then you don't have to use this kind of self-injection.

You can also try to use SessionContext#getBusinessObject() method.

@Resource
SessionContext sessionContext;

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM