如何在container-managed-tx EJB提交时捕获并包装JTA抛出的异常?

Cra*_*ger 21 ejb jpa eclipselink java-ee jboss7.x

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

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

不幸的是,一些约束仅在提交时检查.例如,@Size(min=1)只有在容器管理的事务提交时才会捕获对映射集合的违反,一旦它在我的事务方法结束时离开我的控件.在验证失败时,我不能赶上抛出的异常,敷,因此容器将其包装在一个javax.transaction.RollbackException和包装在诅咒EJBException.调用者必须抓住所有EJBExceptions并在原因链中潜水,以试图找出它是否是验证问题,这真的不太好.

我正在使用容器管理的事务,所以我的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);
       }
    }

}
Run Code Online (Sandbox Code Playgroud)

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

有时我可以触发早期约束违规EntityManager.flush()并捕获,但并非总是如此.即使这样,我真的希望能够捕获数据库级别的约束违规在提交时也被延迟约束检查抛出,而那些只会不断出现时,JTA提交.

救命?


已经尝试过:

Bean托管事务可以通过允许我在代码I控制中触发提交来解决我的问题.不幸的是,它们不是一个选项,因为bean管理的事务不提供任何等价物TransactionAttributeType.REQUIRES_NEW- 没有办法使用BMT暂停事务.JTA令人讨厌的疏忽之一.

看到:

...但请参阅警告和细节的答案.

javax.validation.ValidationException是一个JDK例外; 我不能修改它添加@ApplicationException注释,以防止缠绕.我不能将它子类化以添加注释; 它是由EclpiseLink引发的,而不是我的代码.我不确定它@ApplicationException是否会阻止Arjuna(AS7的JTA impl)将它包裹起来RollbackException.

我尝试使用像这样的EJB3拦截器:

@AroundInvoke
protected Object exceptionFilter(InvocationContext ctx) throws Exception {
    try {
        return ctx.proceed();
    } catch (ValidationException ex) {
        throw new SomeAppException(ex);
    }
}
Run Code Online (Sandbox Code Playgroud)

...但似乎拦截器 JTA 内部发射(这是明智的并且通常是可取的)所以我想要捕获的异常还没有被抛出.

我想我想要的是能够定义一个 JTA完成之后应用的异常过滤器.有任何想法吗?


我正在使用JBoss AS 7.1.1.Final和EclipseLink 2.4.0.根据这些说明,EclipseLink作为JBoss模块安装,但这对于手头的问题并不重要.


更新:经过对这个问题的更多考虑之后,我意识到除了JSR330验证异常之外,我还需要能够分别使用SQLSTATE 40P01和40001从数据库和死锁或序列化故障回滚中捕获 SQLIntegrityConstraintViolationException.这就是为什么一个只是试图确保提交将永远不会抛出的方法将无法正常工作.无法通过JTA提交抛出已检查的应用程序异常,因为JTA接口自然不会声明它们,但未经检查的带注释的异常应该可以.@ApplicationException

似乎在我可以有用地捕获应用程序异常的任何地方,我也可以 - 尽管不那么漂亮 - 捕获EJBException并在其中钻研JTA异常和底层验证或JDBC异常,然后根据它做出决策.如果没有JTA中的异常过滤器功能,我可能不得不这样做.

Cra*_*ger 6

REQUIRES_NEW在原始问题中我对所说的和BMT 有一个警告.

请参阅EJB 3.1规范,部分13.6.1Bean管理事务划分,在集装箱的责任.它写道:

容器必须使用bean管理的事务划分来管理对企业bean实例的客户端调用,如下所示.当客户端通过企业bean的一个客户端视图调用业务方法时,容器会挂起可能与客户端请求关联的任何事务.如果存在与实例关联的事务(如果有状态会话Bean实例在某些先前的业务方法中启动了事务,则会发生这种情况),容器将方法执行与此事务相关联.如果存在与bean实例关联的拦截器方法,则在调用拦截器方法之前执行这些操作.

(斜体矿).这很重要,因为这意味着BMT EJB不会继承具有关联容器托管tx的调用方的JTA tx.任何当前的tx都被挂起,因此如果BMT EJB创建了一个tx,那么它就是一个事务,当它提交时,它只提交它的事务.

这意味着您可以使用BMT EJB方法开始并提交事务,就好像它是有效的REQUIRES_NEW并执行以下操作:

@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);
           }
       }
    }

}
Run Code Online (Sandbox Code Playgroud)

这使得提交在我的控制之下.在我不需要跨多个不同EJB的多个调用的事务的情况下,这允许我在我的代码中处理所有错误,包括提交时的错误.

关于bean托管事务Java EE 6教程页面没有提到这个,或者关于如何调用BMT的任何其他内容.

关于BMT无法REQUIRES_NEW在我链接的博客中进行模拟的讨论是有效的,只是不清楚.如果您有一个bean管理的事务EJB,则无法挂起您开始的事务以便开始另一个事务.调用单独的帮助程序EJB可能会暂停你的tx并给你相同的REQUIRES_NEW但我还没有测试过.


问题的另一半 - 我需要容器管理事务的情况,因为我必须在几个不同的EJB和EJB方法中完成工作 - 通过防御性编码解决.

早期急切刷新实体管理器允许我捕获我的JSR330验证规则可以找到的任何验证错误,所以我只需要确保它们是完整和全面的,所以我从来没有从DB获得任何检查约束或完整性违规错误提交时间.我不能干净利落地处理它们所以我需要真正地防御它们.

防御性编码的一部分是:

  • javax.validation在实体字段上大量使用注释,并且@AssertTrue在不够的情况下使用验证方法.
  • 支持对集合的验证限制,我很高兴看到它.例如,我有A一个集合的实体B.A必须至少有一个 B,所以我在它定义@Size(min=1)B位置的集合中添加了一个约束A.
  • 自定义JSR330验证器.我为澳大利亚商业号码(ABN)添加了几个自定义验证器,以确保我从不尝试向数据库发送无效的验证器并触发数据库级验证错误.
  • 早期冲洗实体经理EntityManager.flush().这会强制验证在代码的控制下进行,而不是在JTA提交事务时.
  • 我的EJB外观中特定于实体的防御性代码和逻辑,以确保不会出现JSR330验证无法检测到的情况并导致提交失败.
  • 在可行的情况下,使用REQUIRES_NEW强制早期提交的方法并允许我在EJB中处理失败,适当地重试等等.这有时需要帮助EJB来解决自我调用业务方法的问题.

我仍然不能优雅地处理和重试序列化失败或死机,就像我直接使用Swing一样使用JDBC,因此所有这些来自容器的"帮助"让我在某些方面向后退了几步.但是,它在其他地方节省了大量繁琐的代码和逻辑.

在发生这些错误的地方,我添加了一个UI框架级异常过滤器.它看到EJBException包装JTA RollbackException包装PersistenceException包装的特定于EclipseLink的异常PSQLException,检查SQLState,并根据它做出决定.这是荒谬的回旋,但它的确有效


Has*_*lan 5

我没试过这个。但我猜这应该有效。

@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);
    }    
}
Run Code Online (Sandbox Code Playgroud)

容器有望启动新的事务,并承诺methodOfInterest,因此,你应该能够赶上在包装方法例外。

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

  • 为自调用添加相同类/接口的`@Inject`ed `self` 实例字段。那么哈桑的解决方案应该有效。 (2认同)