Tuesday, 11 January 2011

Transactions and Coding MDBs

Recently I posted a small blog on how to use setRollbackOnly() to inform your JEE container that your MDB has encountered an error, and that doing this was the preferred method of error handling when using Container Managed Transactions.

It seems to me that although many of the EJB technical books tell you how to write a simple MDB implementation using the MessageListener interface they usually don’t mention transactions in any detail or assume Container Managed Transactions from the start.

This blog describes the Java code required to successfully deal with the three basic transaction types:

  • Container Managed Transactions (CMT)
  • Bean Managed Transactions (BMT)
  • No Transactions

Now, you may think it strange that it’s advisable, or necessary to tightly couple your Java code to the transaction paradigm you’re using, but that’s just the way things are...

The code snippets below assume that we’re coding a simple MDB based on the Façade pattern - in that it delegates the business gunk to some underlying service object which we can safely ignore. The only stipulation we must put on this service is that it throws an exception if something goes wrong.

Container Managed Transaction Code


In this scenario, the JEE container will create and begin a transaction for you before your onMessage() method is called. If all goes well, then the container will commit the transaction once you’ve finished mucking about with your business processing.

This is the scenario described in most books, mainly because it’s the simplest. The simplified code stub below demonstrates that you don’t really need to do much when using Container Managed Transactions.
private MessageDrivenContext ctx;

    @Override
    public void onMessage(Message msg) {

        logger.debug("ContainerManagedTransaction : onMessage : entry");

        try {

            myBusinessService.onMessage(msg);

        } catch (Exception e) {
            logger.error("Whoops an error",e);
            ctx.setRollbackOnly();
        } finally {
            logger.debug("ContainerManagedTransaction : onMessage : exit");
        }
    }
   

As I previously blogged, should an error occur, then call ctx.setRollbackOnly(), at least on Weblogic servers, to inform the JEE container of the error. You can throw an exception, but that causes the container to rollback the transaction AND then to destroy and re-create the MDB, all of which is time consuming.

Bean Managed Transaction Code


In this scenario, there’s been a bit of a glitch and, for some reason you couldn’t take the simple route of using a Container Managed Transaction so, you’re doing the transaction management yourself from within the bean’s Java code. Now, there are probably several ways of doing this, including using Spring’s rinky-dinky transaction classes, but the code below demonstrates how to use a transaction that’s been provided by the container, but managing it in your own Java code.

private MessageDrivenContext ctx;

    @Override
    public void onMessage(Message msg) {

        logger.debug("BeanManagedTransaction : onMessage : entry");
        UserTransaction ut = ctx.getUserTransaction();

       try {
            ut.begin();
           myBusinessService.onMessage(msg);
            ut.commit();
            msg.acknowledge();
        } catch (Exception e) {
            logger.error("Whoops an error",e);
            ut.rollback();
        } finally {
            logger.debug("BeanManagedTransaction : onMessage : exit");
        }
    }
 

In this code, the UserTransaction is provided by:

UserTransaction ut = ctx.getUserTransaction();

and then your code starts the transaction by calling:

ut.begin();

once your business code has done its job then you call

ut.commit();
msg.acknowledge();


to commit your transaction and inform the container that you’ve process the message. In the case of an error, you can rollback the transaction by calling:

ut.rollback();

Code without Transactions


In this scenario you’re not using transactions at all.

private MessageDrivenContext ctx;

    @Override
    public void onMessage(Message msg) {

        logger.debug("NoTransaction : onMessage : entry");

        try {
            myBusinessService.onMessage(msg);

            // No transactions, so acknowledge the message...
            msg.acknowledge();
        } catch (Exception e) {
            logger.error("Whoops an error",e);
        } finally {
            logger.debug("NoTransaction : onMessage : exit");
        }
    }
 

In the code above, the only thing to do is to acknowledge the message by calling:

msg.acknowledge();

The problems of not using transactions, including the probability of duplicate messages are beyond this blog.

If there are any recommendations to be made in this blog, then it’s to apply the Keep It Simple Stupid rule and always try to use Container Managed Transactions wherever possible.