Search code examples
javajakarta-eetransactionsejb

Java EE 7 - How start a transaction from inside a container?


I'm using a Java EE 7 + GlassFish and need to perform some operation against a number of JPA entities from a stateless bean.

@Stateless
public class JobRunner 
{
    public void do()
    {
            for (Entity entity:facade.findAll()) 
            {
                ///do some work against entity
            }
    }
}

This JobRunner bean is injected into the servlet and I invoke do() method from the web UI.

The issue is that all entities are being changed within one transaction so if one fails everything is rolled back what is not desirable. Is there a way to start and close a new transaction for each entity (i.e. for each iteration of the loop)?

I can write an external client and make a loop there calling a stateless bean for each entity but it's not something that completely works for me as I prefer to keep an app monolithic. Can I somehow manage transactions form inside a container?

Maybe JMS helps? If I implement a doer as message listener and will be sending a message for each entity, will it start a new transaction for each one?

@Stateless
public class JobRunner 
{
    public void do()
    {
            for (Entity entity:facade.findAll()) 
            {
                sendMessageToRealDoer(entity);
            }
    }
}

Solution

  • Create another bean, specifying @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW), at method or bean level:

    import javax.ejb.TransactionAttribute;
    import javax.ejb.TransactionAttributeType;
    
    @Stateless
    public class JobWork {
        @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
        public void doWork(Entity entity) {
            // do what you would do in the loop with the Entity
            // this runs in a new transaction
        }
    }
    

    I wish I could tell you that you only need to annotate a method of the same bean (JobRunner) and simply call it. This is not possible (EDIT)without workarounds - check comment from Steve C(/EDIT) because when calling methods of this object in EJBs and CDI beans the interceptors do not get called. Transactions are implemented with interceptors in both cases.

    Some notes:

    • If the total duration of the operations in the loop is expected to be long, you will get a timeout in the outer transaction, that is implicitly started for the JobRunner stateless EJB. You will want to take measure that no "outer" transaction is started.
    • Sending the data to a queue will work too; but queues will process them asynchronously, meaning that the execution will return to the servlet calling JobRunner.do() most probably before all items have been processed.