Exception of failures within a Bean-managed message (MDB)

How do I handle exceptions inside mdb? I have a strange feeling that the exception occurs after the catch catch block, so I cannot catch it and log it. Glassfish v3 decides to repeat the whole message. It runs in an endless loop and writes a lot of log files to the hard drive.

I am using 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
source share
3 answers

You have a bad case of message poisoning ...

The main problems that I see are:

  • you directly call the save() method in onMessage() : this means that there is no way in the container to insert the correct transaction processing proxy using the save method
  • in any case, the save() method must have @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) for fixing in a separate transaction, otherwise it joins the onMessage transaction (by default - REQUIRED ) and bypasses your exception handling code, beign after successful execution of onMessage

What I want to do:

Move the save method to a new session without a bean state:

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

Add this bean to your MDB:

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

One more tip: message poisoning still exists in this code. Now this comes from the line calling mdc.setRollbackOnly(); .

I propose here to register an exception and send the message to a poisonous queue, thereby preventing the ad infinitum from sending the message again.

UPDATE:

A poison queue or an error queue simply means that your (hopefully recoverable) discarded messages will not be completely lost. It is heavily used in integration scenarios where the correctness of the message data is not guaranteed.

Setting up a poison queue involves defining a queue or destination topic and adding “bad” messages to that destination.

Periodically, the operator must check this queue (through a special application), as well as change messages and resend to a “good” queue, or discard the message and request an update.

+5
source

I believe that the code you posted is basically fine.

Using

  @TransactionAttribute(TransactionAttributeType.REQUIRED) 

completely ignored, because this (and most other) annotations can only be applied to business methods (including onMessage). It doesn't matter because your onMessage method gets implicit free.

This causes message processing to be transactional in the Java EE container. If the transaction failed with any reason, the container should try to send the message again.

Now your code catches the exception from the save method, which is good. But then you explicitly mark the transaction for rollback. This leads to the fact that the container reports that the message cannot be delivered and that it must try again.

Therefore, if you remove:

  mdc.setRollbackOnly(); 

the container will stop trying to resend the message.

+2
source

If I am not mistaken, you allow the container to process transactions. Thus, the object manager will stop operations that will be cleared after the method completes, so you have exceptions after the method finishes.

Using em.flush() directly as the last step in the method, all related transaction requests will be executed, throwing exceptions there instead of being thrown later when flush() is created by the container when the transaction is completed.

+1
source

All Articles