23

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

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

不幸的是,一些约束只在提交时检查。例如,@Size(min=1)仅当容器管理的事务提交时,一旦它在我的事务方法结束时离开我的控制权,才会捕获对映射集合的违反。我无法捕获验证失败时抛出的异常并将其包装,因此容器将其包装在 a 中javax.transaction.RollbackException并将其包装cursedEJBException中。调用者必须捕获所有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);
       }
    }

}

...AppValidationException已检查异常或未检查异常在哪里进行了注释@ApplicationException,因此它不会被 EJB3 包装。

有时我可以用 an 触发早期的约束违规EntityManager.flush()并抓住它,但并非总是如此。即使这样,我也真的希望能够在提交时捕获由延迟约束检查引发的数据库级约束违规,并且这些只会在JTA提交时出现。

帮助?


已经尝试过:

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

看:

...但请参阅注意事项和详细信息的答案。

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

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

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

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

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


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


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

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

4

4 回答 4

7

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

请参阅容器职责中的 EJB 3.1 规范,第13.6.1 节 Bean-Managed Transaction Demarcation。上面写着:

容器必须使用 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);
           }
       }
    }

}

这将提交置于我的控制之下。我不需要事务来跨越多个不同 EJB 的多个调用,这允许我在我的代码中处理所有错误,包括提交时的错误。

关于 bean 管理事务的Java EE 6 教程页面没有提及这一点,也没有提及如何调用 BMT。

我链接到的博客中关于 BMT 无法模拟REQUIRES_NEW的讨论是有效的,只是不清楚。如果您有一个 bean 管理的事务性 EJB,那么您不能暂停您开始的事务以开始另一个事务。调用单独的帮助程序 EJB 可能会暂停您的 tx 并为您提供等价物,REQUIRES_NEW但我还没有测试过。


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

实体管理器的早期急切刷新允许我捕获我的 JSR330 验证规则可以找到的任何验证错误,所以我只需要确保它们是完整和全面的,这样我就不会从数据库中收到任何检查约束或完整性违规错误提交时间。我不能干净地处理它们,所以我需要真正防御性地避开它们。

防御性编码的一部分是:

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

我仍然不能像直接将 JDBC 与 Swing 一起使用时那样优雅地处理和重试序列化失败或死锁,因此容器的所有这些“帮助”使我在某些方面倒退了几步。不过,它在其他地方节省了大量繁琐的代码和逻辑。

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

于 2012-08-06T04:26:30.190 回答
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);
    }    
}

容器预计将在 中启动新事务并提交因此methodOfInterest您应该能够在包装器方法中捕获异常。

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

于 2012-08-03T01:01:27.410 回答
2

eclipselink.exception-handler属性设置为指向ExceptionHandler看起来很有希望的实现,但没有成功。

JavaDoc for ExceptionHandleris ... bad ... 所以您需要查看测试实现和使用它的测试(12)。这里有一些更有用的文档。

似乎很难使用异常过滤器来处理一些特定情况,同时不影响其他所有情况。我想捕获PSQLException,检查 SQLSTATE 23514(CHECK违反约束),为此抛出一个有用的异常,否则不会改变任何东西。这看起来不实用。

最后,我放弃了这个想法,尽可能地使用 bean 管理事务(现在我正确理解了它们的工作原理)和一种防御方法,以防止在使用 JTA 容器管理事务时出现不需要的异常。

于 2012-08-06T04:51:32.110 回答
2

javax.validation.ValidationException 是 JDK 异常;我无法修改它以添加 @ApplicationException 注释以防止包装

除了您的答案:您可以使用 XML 描述符将 3rd 方类注释为 ApplicationException。

于 2013-10-24T14:25:42.893 回答