EJB3 Transaction Propagation

无人久伴 提交于 2019-11-28 17:15:52

Another way to do it is actually having both methods on the same bean - and having an @EJB reference to itself! Something like that:

// supposing processObjects defined on MyStatelessRemote1 and process defined on MyStatelessLocal1
@Stateless
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 {
    @EJB
    private MyStatelessLocal1 myBean2;

    public void processObjects(List<Object> objs) {
        // this method just processes the data; no need for a transaction
        for(Object obj : objs) {
            this.myBean2.process(obj);
        }
    }


    @TransationAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void process(Object obj) {
        // do some work with obj that must be in the scope of a transaction

        this.mgr.merge(obj);
        // ...
        this.mgr.merge(obj);
        // ...
        this.mgr.flush();
    }
}

This way you actually 'force' the process() method to be accessed via the ejb stack of proxies, therefore taking the @TransactionAttribute in effect - and still keeping only one class. Phew!

bluecarbon

Matt, the question you ask is a pretty classic one, I think the self-reference solution by Herval/Pascal is neat. There is a more general solution not mentioned here.

This is a case for EJB "user" transactions. Since you are in a session bean you can get the user transaction from the session context. Here's how your code will look with user transactions:

// supposing processObjects defined on MyStatelessRemote1 and process defined on MyStatelessLocal1
@Stateless
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 {

    @Resource
    private SessionContext ctx;

    @EJB
    private MyStatelessLocal1 myBean2;

    public void processObjects(List<Object> objs) {
        // this method just processes the data; no need for a transaction
        for(Object obj : objs) {
            this.myBean2.process(obj);
        }
    }


    public void process(Object obj) {

        UserTransaction tx = ctx.getUserTransaction();

        tx.begin();

        // do some work with obj that must be in the scope of a transaction

        this.mgr.merge(obj);
        // ...
        this.mgr.merge(obj);
        // ...
        this.mgr.flush();

        tx.commit();
    }
}

I think the thing is each bean is wrapped in a proxy that controls the transactional behaviour. When you call from one bean to another, you're going via that bean's proxy and the transaction behaviour can be changed by the proxy.

But when a bean calls a method on itself with a different transaction attribute, the call doesn't go via the proxy, so the behaviour doesn't change.

Matt, for what it's worth I've come to exactly the same conclusion as you.

TransactionAttributeTypes are only taken into consideration when crossing Bean boundaries. When calling methods within the same bean TransactionAttributeTypes have no effect, no matter what Types are put on the methods.

As far as I can see there is nothing in the EJB Persistence Spec that specifies what the behaviour should be under these circumstances.

I've also experienced this in Jboss. I'll also give it a try in Glassfish and let you know the results.

In case someone comes across this one day:

to avoid circular dependencies (allowing self reference for example) in JBoss use the annotation 'IgnoreDependency' for example:

@IgnoreDependency @EJB MySelf myselfRef;

tizzo

I haven't tried it yet (I'm about to), but an alternative to injecting a self-reference via the @EJB annotation is the SessionContext.getBusinessObject() method. This would be another way to avoid the possibility of a circular reference blowing things up on you - although at least for stateless beans injection does seem to work.

I'm working on a large system in which both techniques are employed (presumably by different developers), but I'm not sure which is the "correct" way to do it.

I think has to do with the @TransationAttribute(TransactionAttributeType.Never) on method processObjects.

TransactionAttributeType.Never

http://docs.sun.com/app/docs/doc/819-3669/6n5sg7cm3?a=view

If the client is running within a transaction and invokes the enterprise bean’s method, the container throws a RemoteException. If the client is not associated with a transaction, the container does not start a new transaction before running the method.

I assume that you are client the method processObjects from the client code. Because probably your client is not associated with a transaction the method call with TransactionAttributeType.Never is happy in the first place. Then you call the process method from processObjects that altough having the TransactionAttributeType.Required annotation was not a bean method call and the transaction policy is not enforced. When you call merge you get the exception because you are still not associated with a transaction.

Try using TransactionAttributeType.Required for both bean methods to see if it does the trick.

I had these circular dependency issues which Kevin mentioned. However, the proposed annotation @IgnoreDependency is a jboss-specific annotation and there is no counterpart in e.g Glassfish.

Since it does not work with default EJB reference, I felt a bit uncomfortable with this solution.

Therefore, I gave bluecarbon's solution a chance, thus starting the inner transaction "by hand".

Beside this, I see no solution but to implement the inner process() in another bean which is also ugly because we simply want to disturb our class model for such technical details.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!