views:

3138

answers:

7

I have a stateless bean something like:

@Stateless
public class MyStatelessBean implements MyStatelessLocal, MyStatelessRemote {
    @PersistenceContext(unitName="myPC")
    private EntityManager mgr;

    @TransationAttribute(TransactionAttributeType.SUPPORTED)
    public void processObjects(List<Object> objs) {
        // this method just processes the data; no need for a transaction
        for(Object obj : objs) {
            this.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();
    }
}

The typically usage then is the client would call processObjects(...), which doesn't actually interact with the entity manager. It does what it needs to do and calls process(...) individually for each object to process. The duration of process(...) is relatively short, but processObjects(...) could take a very long time to run through everything. Therefore I don't want it to maintain an open transaction. I do need the individual process(...) operations to operate within their own transaction. This should be a new transaction for every call. Lastly I'd like to keep the option open for the client to call process(...) directly.

I've tried a number of different transaction types: never, not supported, supported (on processObjects) and required, requires new (on process) but I get TransactionRequiredException every time merge() is called.

I've been able to make it work by splitting up the methods into two different beans:

@Stateless
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 {
    @EJB
    private MyStatelessBean2 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);
        }
    }
}

@Stateless
public class MyStatelessBean2 implements MyStatelessLocal2, MyStatelessRemote2 {
    @PersistenceContext(unitName="myPC")
    private EntityManager mgr;

    @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();
    }
}

but I'm still curious if it's possible to accomplish this in one class. It looks to me like the transaction manager only operates at the bean level, even when individual methods are given more specific annotations. So if I mark one method in a way to prevent the transaction from starting calling other methods within that same instance will also not create a transaction, no matter how they're marked?

I'm using JBoss Application Server 4.2.1.GA, but non-specific answers are welcome / preferred.

A: 

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.

smink
As I understand it smink you miss the point. Matt doesn't want processObjects to run in a transation (which would be the case if he set TransactionAttributeType.Required on processObjects), he wants many individual transactions for the process method.
montyontherun
A: 

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.

montyontherun
+2  A: 

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.

fiddlesticks
By that, the bean could call itself to get that behavior, as if it was from another bean.
Loki
+8  A: 

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!

Just thought I'd drop by and share that since asking this question this problem has come up a number of times (incidentally, my boss said he found this question via Google once while trying to solve it himself.) We have used this solution a number of times, so thanks again.
Matt S.
I think this is a good question to know. You expect these semantics if you ever had to do anything from the EJB2 world, but they may seem a little foreign if you've only worked in EJB3. Another way to look at things is that you need to make sure the method you're calling is called *through the EJB interface*.
cwash
A: 

Hi all,

i'm trying the solution proposed (@EJB annotation injecting a reference of itself) but at deploy time on jboss 5.1 i have an error about Missing dependencies (should be in state described but is in state preinstall).

Elvis D.
+1  A: 

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

        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();
    }
}
bluecarbon
A: 

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;

Kevin