5

我必须如何处理 mdb 中的异常?我有一种有趣的感觉,异常发生在 try catch 块之后,所以我无法捕获并记录它。Glassfish v3 决定重复整个消息。它运行到一个无限循环并在硬盘驱动器上写入大量日志文件。

我正在使用 Glassfishv3.01 + Eclipselink 2.0.1

public class SaveAdMessageDrivenBean implements MessageListener {

    @PersistenceContext(unitName="QIS") 
    private EntityManager em;

    @Resource
    private MessageDrivenContext mdc;

    public void onMessage(Message message) {
        try {
            if (message instanceof ObjectMessage) {
                ObjectMessage obj = (ObjectMessage)message;
                AnalyzerResult alyzres = (AnalyzerResult)obj.getObject();
                save(alyzres);
            }
        } catch (Throwable e) { 
            mdc.setRollbackOnly();
            log.log(Level.SEVERE, e);
        }
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    private void save(AnalyzerResult alyzres) throws PrdItemNotFoundException {

       Some s = em.find(Some.class, somepk);
       s.setSomeField("newvalue");

       // SQL Exception happens after leaving this method because of missing field for ex.
    }
}    
4

3 回答 3

5

您遇到了消息中毒的严重案例...

我看到的主要问题是:

  • 您正在直接调用您的save()方法:这意味着容器无法在方法onMessage()周围注入正确的事务处理代理save
  • 在任何情况下,该save()方法都应该具有 @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)以便在单独的事务中提交,否则它将加入onMessage事务(默认为REQUIRED)并绕过您的异常处理代码,在成功执行后提交onMessage

我要做的是:

将该方法移动save到一个新的无状态会话 bean:

@Stateless
public class AnalyzerResultSaver
{
    @PersistenceContext(unitName="QIS") 
    private EntityManager em;

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    private void save(AnalyzerResult alyzres) throws PrdItemNotFoundException {
        Some s = em.find(Some.class, somepk);
        s.setSomeField("newvalue");
        // SQL Exception happens after leaving this method
    }
}

在你的 MDB 中注入这个 bean:

public class SaveAdMessageDrivenBean implements MessageListener {

    @Inject  
    private AnalyzerResultSaver saver;

    @Resource
    private MessageDrivenContext mdc;

    public void onMessage(Message message) {
        try {
            if (message instanceof ObjectMessage) {
                ObjectMessage obj = (ObjectMessage)message;
                AnalyzerResult alyzres = (AnalyzerResult)obj.getObject();
                saver.save(alyzres);
            }
        } catch (Throwable e) { 
            mdc.setRollbackOnly();
            log.log(Level.SEVERE, e);
        }
    }
}

另一个提示:在此代码中,消息中毒仍然存在。现在它源自调用mdc.setRollbackOnly();.

我建议在这里记录异常并将消息传输到毒队列,从而防止容器无限地重新提交消息。

更新:

“毒药队列”或“错误队列”只是保证您的(希望可恢复的)丢弃消息不会完全丢失的一种手段。它在无法保证消息数据正确性的集成场景中大量使用。

设置有害队列意味着定义目标队列或主题并将“坏”消息重新传递到该目标。

操作员应定期检查此队列(通过专用应用程序)并修改消息并重新提交到“好”队列,或者丢弃消息并要求重新提交。

于 2012-12-12T13:19:10.583 回答
2

我相信您发布的代码大部分都可以。

您的使用

    @TransactionAttribute(TransactionAttributeType.REQUIRED)

被完全忽略,因为这个(和大多数其他)注释只能应用于业务方法(包括 onMessage)。不过这并不重要,因为您的 onMessage 方法会免费获得一个隐式方法。

这导致消息处理在 Java EE 容器中是事务性的。如果事务因任何原因失败,则容器需要尝试再次传递消息。

现在,您的代码正在从 save 方法中捕获异常,这很好。但是随后您将明确标记事务以进行回滚。这具有告诉容器消息传递失败并且它应该重试的效果。

因此,如果您删除:

    mdc.setRollbackOnly();

容器将停止尝试重新传递消息。

于 2012-12-12T13:37:57.630 回答
1

如果我没记错的话,你让容器处理交易。这样,实体管理器将在方法完成后将要刷新的操作排队,这就是方法完成后出现异常的原因。

直接使用em.flush()作为方法的最后一步将执行事务的所有相关查询,在那里抛出异常,而不是稍后在flush()提交事务时由容器生成时抛出异常。

于 2012-12-12T12:56:44.353 回答