EJB 中的嵌套方法调用试图 start/create 嵌套方法调用中的新事务

Nested method calls in EJBs attempting to start/create a new transaction in a nested method call

让我们考虑在无状态 EJB 中使用 @TransactionAttribute(TransactionAttributeType.REQUIRED)(默认)注释的两种方法 methodA() 和使用 @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 注释的 methodB()

通过 methodA()methodB() 进行嵌套调用不会 start/create 一个新事务,因为它看起来是(无论目标方法使用哪种事务属性类型),因为从 methodA()methodB() 的嵌套调用使用 this pointer/reference(即实际的 EJB 实例)来调用 methodB(),因此不会被代理拦截将由容器在 运行 时注入,并且需要在调用方法之前设置环境。

基本演示:

@Stateless
public class TestBean implements TestBeanService {

    @PersistenceContext
    private EntityManager entityManager;

    // At a glance, this method should cause an exception but it does not.
    @Override
    @TransactionAttribute(TransactionAttributeType.NEVER)
    public Long getRowCount() {
        return entityManager.createQuery("SELECT count(e) AS cnt FROM Employee e", Long.class).getSingleResult();
    }

    // This method is invoked by the application client.
    // making a nested call to getRowCount() using the "this" pointer.
    @Override
    public void test() {
        Long rowCount = getRowCount();
        System.out.println("rowCount : " + rowCount);
    }
}

虽然getRowCount()方法用@TransactionAttribute(TransactionAttributeType.NEVER)修饰,一看就应该是异常,但是成功returns查询返回的行数

► 这是因为由 test() 方法启动的事务被传播(扩展)到 getRowCount(),即一切都发生在同一个事务中。


但是,如果使用通过 javax.ejb.SessionContext 获得的代理实例调用 getRowCount() 方法,则会抛出异常。以下代码段演示了此修改。

@Stateless
public class TestBean implements TestBeanService {

    @PersistenceContext
    private EntityManager entityManager;

    @Resource
    private SessionContext sessionContext;

    @Override
    @TransactionAttribute(TransactionAttributeType.NEVER)
    public Long getRowCount() {
        return entityManager.createQuery("SELECT count(e) AS cnt FROM Employee e", Long.class).getSingleResult();
    }

    @Override
    public void test() {
        // Invocation to getRowCount() is done by a proxy instance. Hence, it causes an exception,
        // since the transaction started by this method is now not propagated to a subsequent call to getRowCount().
        Long rowCount = sessionContext.getBusinessObject(TestBeanService.class).getRowCount();
        System.out.println("rowCount : " + rowCount);
    }
}

由于getRowCount()方法使用了TransactionAttributeType.NEVER,上面对getRowCount()的方法调用导致抛出以下与第一种情况相矛盾的异常。

javax.ejb.EJBException: EJB cannot be invoked in global transaction

► 这是因为由 test() 方法启动的事务现在 传播(扩展)到 getRowCount(),就像在第一种情况下发生的那样,因为现在通过代理实例调用此方法(因此,不是像往常一样直接通过 this 指针 - 实际的 EJB 实例)。

通过javax.ejb.SessionContext获取代理实例并在该代理实例上调用方法是一种 hack。我认为它不应该用于实际应用程序。

是否有任何其他方法可以在需要时 start/create 在嵌套方法调用中创建新事务?

事务属性仅在您通过 interface/proxy 访问 bean 时才有效,而不是像您观察到的那样作为内部方法。因此,除了通过 SessionContext 访问它之外,您还可以在 class 中有一个参考:@EJB TestBeanService serviceBean 并通过 serviceBean.getRowCount() 访问它(也是一种 hack)。