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)。
让我们考虑在无状态 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)。