Java EE 7 - 如何从容器内部启动事务?

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

我正在使用 Java EE 7 + GlassFish,并且需要对无状态 bean 中的许多 JPA 实体执行一些操作。

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

这个 JobRunner bean 被注入到 servlet 中,我从网络调用 do() 方法 UI。

问题是所有实体都在一个事务中被更改,因此如果一个事务失败,所有内容都会回滚,这是不希望的。有没有办法为每个实体(即循环的每次迭代)启动和关闭新事务?

我可以编写一个外部客户端并在其中创建一个循环,为每个实体调用一个无状态 bean,但这对我来说并不是完全可行的,因为我更喜欢保持应用程序单一。我能以某种方式管理容器内的事务表单吗?

也许 JMS 有帮助?如果我将执行者实现为消息侦听器并将为每个实体发送一条消息,它会为每个实体启动一个新事务吗?

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

创建另一个 bean,在方法或 bean 级别指定 @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)

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
    }
}

我希望我能告诉你,你只需要注释同一个 bean (JobRunner) 的方法并简单地调用它。这是不可能的 (EDIT)without workarounds - 检查 Steve C(/EDIT) 的评论,因为在 EJB 和 CDI bean 中调用 this 对象的方法时,拦截器不会得到叫。在这两种情况下,事务都是用拦截器实现的。

一些注意事项:

  • 如果预期循环中操作的总持续时间很长,您将在 outer 事务中获得超时,该事务隐式启动 JobRunner 无状态 EJB。您需要采取措施确保没有 "outer" 事务启动。
  • 将数据发送到队列也可以;但是队列将异步处理它们,这意味着执行将 return 到 servlet 调用 JobRunner.do() 很可能在所有项目都被处理之前。