Jakarta ee 当容器管理的tx EJB提交时,如何捕获和包装JTA抛出的异常?

Jakarta ee 当容器管理的tx EJB提交时,如何捕获和包装JTA抛出的异常?,jakarta-ee,jpa,ejb,eclipselink,jboss7.x,Jakarta Ee,Jpa,Ejb,Eclipselink,Jboss7.x,我正在努力解决一个管理非平凡数据模型的EJB3类的问题。当容器管理的事务方法提交时,会引发约束验证异常。我想防止它们被包装在EJBException中,而不是抛出调用方可以处理的sane应用程序异常 要将其封装在合适的应用程序异常中,我必须能够捕获它。大多数情况下,一个简单的try/catch可以完成这项工作,因为验证异常是从我所做的EntityManager调用中抛出的 不幸的是,某些约束仅在提交时检查。例如,只有在容器管理的事务提交时,当它在事务方法的末尾离开我的控制时,才会捕获映射集合上的

我正在努力解决一个管理非平凡数据模型的EJB3类的问题。当容器管理的事务方法提交时,会引发约束验证异常。我想防止它们被包装在
EJBException
中,而不是抛出调用方可以处理的sane应用程序异常

要将其封装在合适的应用程序异常中,我必须能够捕获它。大多数情况下,一个简单的try/catch可以完成这项工作,因为验证异常是从我所做的
EntityManager
调用中抛出的

不幸的是,某些约束仅在提交时检查。例如,只有在容器管理的事务提交时,当它在事务方法的末尾离开我的控制时,才会捕获映射集合上的违反
@Size(min=1)
。我无法捕获验证失败时抛出的异常并将其包装,因此容器将其包装在
javax.transaction.RollbackException
中,并将其包装在诅咒的
EJBException
中。调用者必须捕获所有的
EJBException
s并深入原因链,试图找出这是否是一个验证问题,这真的不好

我正在处理容器管理的事务,因此我的EJB如下所示:

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER
class TheEJB {

    @Inject private EntityManager em;

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public methodOfInterest() throws AppValidationException {
       try {
           // For demonstration's sake create a situation that'll cause validation to
           // fail at commit-time here, like
           someEntity.getCollectionWithMinSize1().removeAll();
           em.merge(someEntity);
       } catch (ValidationException ex) {
           // Won't catch violations of @Size on collections or other
           // commit-time only validation exceptions
           throw new AppValidationException(ex);
       }
    }

}
@AroundInvoke
protected Object exceptionFilter(InvocationContext ctx) throws Exception {
    try {
        return ctx.proceed();
    } catch (ValidationException ex) {
        throw new SomeAppException(ex);
    }
}
@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
class TheEJB {

    @Inject private EntityManager em;

    @Resource private UserTransaction tx; 

    // Note: Any current container managed tx gets suspended at the entry
    // point to this method; it's effectively `REQUIRES_NEW`.
    // 
    public methodOfInterest() throws AppValidationException, SomeOtherAppException {
       try {
           tx.begin();
           // For demonstration's sake create a situation that'll cause validation to
           // fail at commit-time here, like
           someEntity.getCollectionWithMinSize1().removeAll();
           em.merge(someEntity);
           tx.commit();
       } catch (ValidationException ex) {
           throw new AppValidationException(ex);
       } catch (PersistenceException ex) {
           // Go grubbing in the exception for useful nested exceptions
           if (isConstraintViolation(ex)) {
               throw new AppValidationException(ex);
           } else {
               throw new SomeOtherAppException(ex);
           }
       }
    }

}
。。。其中,
AppValidationException
是一个已检查的异常或一个未检查的异常,注释为
@ApplicationException
,因此它不会被EJB3包装

有时,我可以使用
EntityManager.flush()
触发早期约束冲突并捕获它,但并不总是这样。即使这样,我也希望能够捕获延迟约束检查在提交时抛出的数据库级约束冲突,而这些冲突只有在JTA提交时才会出现

帮忙


已尝试:

Bean管理的事务将通过允许我在我控制的代码中触发提交来解决我的问题。不幸的是,它们不是一个选项,因为bean管理的事务不提供任何等价的
TransactionAttributeType.REQUIRES\u NEW
-无法使用BMT挂起事务。JTA令人讨厌的疏忽之一

见:

  • (不要这样做!)
。。。但请参见答案,了解注意事项和详细信息

javax.validation.ValidationException
是一个JDK异常;我无法修改它以添加一个
@ApplicationException
注释来防止包装。我不能将其子类化以添加注释;这是EclpiseLink抛出的,不是我的代码。我不确定标记它
@ApplicationException
是否会阻止Arjuna(AS7的JTA impl)将其包装成
回滚异常

我尝试使用一个EJB3拦截器,如下所示:

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER
class TheEJB {

    @Inject private EntityManager em;

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public methodOfInterest() throws AppValidationException {
       try {
           // For demonstration's sake create a situation that'll cause validation to
           // fail at commit-time here, like
           someEntity.getCollectionWithMinSize1().removeAll();
           em.merge(someEntity);
       } catch (ValidationException ex) {
           // Won't catch violations of @Size on collections or other
           // commit-time only validation exceptions
           throw new AppValidationException(ex);
       }
    }

}
@AroundInvoke
protected Object exceptionFilter(InvocationContext ctx) throws Exception {
    try {
        return ctx.proceed();
    } catch (ValidationException ex) {
        throw new SomeAppException(ex);
    }
}
@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
class TheEJB {

    @Inject private EntityManager em;

    @Resource private UserTransaction tx; 

    // Note: Any current container managed tx gets suspended at the entry
    // point to this method; it's effectively `REQUIRES_NEW`.
    // 
    public methodOfInterest() throws AppValidationException, SomeOtherAppException {
       try {
           tx.begin();
           // For demonstration's sake create a situation that'll cause validation to
           // fail at commit-time here, like
           someEntity.getCollectionWithMinSize1().removeAll();
           em.merge(someEntity);
           tx.commit();
       } catch (ValidationException ex) {
           throw new AppValidationException(ex);
       } catch (PersistenceException ex) {
           // Go grubbing in the exception for useful nested exceptions
           if (isConstraintViolation(ex)) {
               throw new AppValidationException(ex);
           } else {
               throw new SomeOtherAppException(ex);
           }
       }
    }

}
。。。但截击机似乎在JTA内部发射(这是明智的,通常是可取的),因此我想要捕获的异常还没有抛出

我想我想要的是能够定义一个异常过滤器,在JTA完成它的工作后应用它。有什么想法吗


我正在使用JBoss AS 7.1.1.Final和EclipseLink 2.4.0。EclipseLink按照作为JBoss模块安装,但这对当前的问题没有多大影响


更新:在对这个问题进行了更多思考之后,我意识到除了JSR330验证异常之外,我还需要能够从数据库和数据库中捕获。这就是为什么仅仅试图确保commit永远不会抛出的方法不会很好地工作。检查过的应用程序异常不能通过JTA提交抛出,因为JTA接口自然不会声明它们,但是未检查的
@ApplicationException
注释过的异常应该能够被抛出


似乎在任何可以有效捕获应用程序异常的地方,我也可以捕获一个EJBException,并在其中深入研究JTA异常和底层验证或JDBC异常,然后在此基础上做出决策。如果JTA中没有异常过滤器功能,我可能不得不这样做。

我还没有尝试过这个。但我猜这应该行得通

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
class TheEJB {

    @Inject
    private TheEJB self;

    @Inject private EntityManager em;

    public methodOfInterest() throws AppValidationException {
       try {
           self.methodOfInterestImpl();
       } catch (ValidationException ex) {
           throw new AppValidationException(ex);
       }
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public methodOfInterestImpl() throws AppValidationException {
        someEntity.getCollectionWithMinSize1().removeAll();
        em.merge(someEntity);
    }    
}
容器需要启动一个新事务并在
methodOfInterest
中提交,因此您应该能够在包装器方法中捕获异常


Ps:答案是根据@lairdelson提供的优雅想法更新的…

我在原始问题中所说的
需要新的
和BMT

参见EJB3.1规范,第13.6.1节Bean-Managed事务划分,在容器职责中。内容如下:

容器必须管理对企业bean的客户端调用 具有bean管理的事务划分的实例,如下所示。当 客户机通过企业bean的 在客户端视图中,容器将挂起可能发生的任何事务 与客户端请求关联。如果有交易 与实例关联(如果有状态会话 bean实例在以前的一些业务中启动了事务 方法),容器将方法执行与此 交易如果存在与bean关联的拦截器方法 实例中,这些操作是在侦听器方法启动之前执行的 调用

(斜体)。这很重要,因为这意味着BMT EJB不会继承具有关联容器管理的tx的调用方的JTA tx。任何当前tx都将挂起,因此,如果BMT EJB创建tx,它将是一个新事务,并且在提交时只提交其事务

这意味着您可以使用BMT EJB方法开始并提交事务,就像它是有效的
REQUIRES\u NEW
一样,并执行如下操作:

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER
class TheEJB {

    @Inject private EntityManager em;

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public methodOfInterest() throws AppValidationException {
       try {
           // For demonstration's sake create a situation that'll cause validation to
           // fail at commit-time here, like
           someEntity.getCollectionWithMinSize1().removeAll();
           em.merge(someEntity);
       } catch (ValidationException ex) {
           // Won't catch violations of @Size on collections or other
           // commit-time only validation exceptions
           throw new AppValidationException(ex);
       }
    }

}
@AroundInvoke
protected Object exceptionFilter(InvocationContext ctx) throws Exception {
    try {
        return ctx.proceed();
    } catch (ValidationException ex) {
        throw new SomeAppException(ex);
    }
}
@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
class TheEJB {

    @Inject private EntityManager em;

    @Resource private UserTransaction tx; 

    // Note: Any current container managed tx gets suspended at the entry
    // point to this method; it's effectively `REQUIRES_NEW`.
    // 
    public methodOfInterest() throws AppValidationException, SomeOtherAppException {
       try {
           tx.begin();
           // For demonstration's sake create a situation that'll cause validation to
           // fail at commit-time here, like
           someEntity.getCollectionWithMinSize1().removeAll();
           em.merge(someEntity);
           tx.commit();
       } catch (ValidationException ex) {
           throw new AppValidationException(ex);
       } catch (PersistenceException ex) {
           // Go grubbing in the exception for useful nested exceptions
           if (isConstraintViolation(ex)) {
               throw new AppValidationException(ex);
           } else {
               throw new SomeOtherAppException(ex);
           }
       }
    }

}
这个