如何在 Java EE 环境中使用 JMS 和 JPA 处理事务回滚?

How to handle transaction rollback with JMS and JPA in a Java EE environment?

CMT MDB 的默认回滚行为是return将消息发送到目的地,以便再次处理它。

是否可以避免重新传送由托管 MDB 处理的消息,即使事务已回滚? (或者可能配置容器处理的确认行为)。

到目前为止,我想到了以下备选方案:

  1. 将业务事务与消息事务隔离 - 我可以在业务方法上使用 TransactionAttributeType.REQUIRES_NEW 但它创建了一个业务场景 事件可能被处理两次,因为消息可能潜在 业务交易成功后不确认。
  2. 使用 BMT - 与上述相同的问题,因为事务将与消息事务分开。
  3. 使用 JMS 服务器专有配置处理传送失败 - 如果可能,我想将此逻辑保留在应用程序中。 此外,我必须为所有队列处理它,因为 WebLogic 默认 配置是永远重新传递消息。

阅读后 this tutorial 我仍然不确定如何解决这个问题,但到目前为止,使用专有的 WebLogic 控制台控制消息传递失败似乎是正确的选择。在这种情况下,对队列的重新交付设置限制 - 例如:3 次尝试。它会产生处理开销,因为无效的业务事件可能会失败 3 次,但我可以保证系统完整性。

大家怎么看?

详情

我有一个与业务事务集成的 MDB,它使用 JPA(WebLogic 10.3.6 中的 EclipseLink)。一切都是 运行 CMT,交易是分布式的。事务和消息确认由容器控制。

如果 JPA 提供程序中发生异常(例如:非空列的空值),则将重新传送消息,因为提供程序正在回滚事务并且消息未被确认。我是否捕捉到异常并不重要,EclipseLink 无论如何都会回滚事务。我知道这是 JPA 的正确行为。

另外,使用 MessageDrivenContext.getRollbackOnly() returns false。我希望这是真的。

如果我使用 TransactionAttributeType.REQUIRES_NEW 执行我的业务方法,事务将回滚并且不会重新传递消息,但是消息处理事务将是独立的,这也是不希望的。我确实设置了一个 JDBC 存储来将消息保存在数据库中。

我会留下一些假人 类 来说明我的观点。

MDB消息处理

提取有效负载后,我将其转发到会话 bean 以处理持久性逻辑。

public void onMessage(Message message) {

    try {
        // Extract the payload
        TextMessage txtMsg = (TextMessage) message;
        String employeeName = txtMsg.getText();

        // Call service
        service.createEmployee(employeeName);

    } catch (Exception e) {
        e.printStackTrace();            
    } finally {
        // When the JPA provider rollbacks back the transaction, this value
        // is still "false"
        log.info(String.format("Rollback only: [%s]", mdContext.getRollbackOnly()));
    }
}

强制 JPA 提供程序异常

通过在非空字段中留下 null 强制错误。

// Message and business will run in the same transaction
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public void createEmployee(String name) {

    Employee employee = new Employee();
    employee.setName(null); // Null value to force constraint error

    try {
        // This part triggers the exception within the JPA provider, and the
        // Java EE transaction is rolledback and forces the JMS message to be
        // redelivered.
        em.persist(employee);

    } catch (Exception e) {
        // Capturing the exception does not affect the rollback behavior
        e.printStackTrace();
    }
}

这是 EclipseLink 抛出的错误。它包含在 RuntimeException 中,因此它是一个系统异常,事务将回滚。

javax.ejb.EJBTransactionRolledbackException: EJB Exception: ; nested exception is: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.1.v20111018-r10243): org.eclipse.persistence.exceptions.DatabaseException

您使用的是什么 Q 提供程序?

您似乎已经涵盖了各种可能性

恕我直言,最好的解决方案是将使用 JPA 的业务逻辑隔离在具有 "Transaction new" 属性的单独外观 SLSB 中,并仍然使用 CMT 语义,因此如果在此 SLSB 中发生不良情况,您仍然可以提交 tx,其中 onMessage tx 运行 由您的 Application Server

自动启动

这听起来与您的第一个解决方案相似。

我的第二个选择是配置 Q 管理器,这样当消息在 Q 上 "redelivered" 一定次数时(即由于 tx 回滚而放回 Q),消息被移动到另一个 Q(死信队列)(并在此 Q 上配置一些 monitoring/alerting 系统...

Denis(JMSToolBox 的作者)

恕我直言,有 2 笔交易正是您要找的。

有一个事务处理由 EJB 容器启动的 MDB,然后在 MDB 中调用无状态会话 bean (SLSB) 中的业务方法,该方法用 Transaction.REQUIRES_NEW 注释,因此您的业务逻辑处理数据库的事务在其自己的隔离事务中被隔离。

当您从 SLSB 的方法返回时,无论您的业务逻辑方法中发生了什么,MDB tx 都将在 MDB 的 onMessage() 方法结束时由容器提交,消息将是consumed/removed 来自Q. 重新加工的可能性为零...

唯一的例外是 MDB 本身出现问题,但您可以轻松处理

干杯