2
votes

In my Java EE application I have implemented an asynchronous DB logger as an MDB which receives XML messages via JMS and writes them to the DB.

In another MDB I create a log message and send them to the input queue of the logger MDB using the following code:

    public static void log(String correlId, String message, String data) throws Exception{

            SysLogEntry sysLogEntry = new SysLogEntry();
            sysLogEntry.setCorrelId(correlId);
            sysLogEntry.setDatetimeCreate(new Date());
            sysLogEntry.setMessage(message);
            sysLogEntry.setData(data);

            ConnectionFactory jmsConnectionFactory = (ConnectionFactory)initialContext.lookup(JMS_CONNECTION_FACTORY_JNDI_NAME);
            Destination logEventDestination = (Destination) initialContext.lookup(LOG_EVENT_DESTINATION_JNDI_NAME);

            JmsUtils.sendMsgToDestination(JaxbUtils.toString(sysLogEntry, jaxbContext), jmsConnectionFactory, logEventDestination, false, Session.AUTO_ACKNOWLEDGE);

        }

public static void sendMsgToDestination(String payload, ConnectionFactory connFactory, Destination destination, boolean sessionTransacted, int acknowledgeMode) throws JMSException{
        if(payload == null)
            throw new IllegalArgumentException("Message payload is null");
        if(connFactory == null)
            throw new IllegalArgumentException("Connection factory is null");
        if(destination == null)
            throw new IllegalArgumentException("Message destination is null");

        Connection connection = null;
        try{
            connection = connFactory.createConnection();
            Session session = connection.createSession(sessionTransacted, acknowledgeMode);
            MessageProducer messageProducer = session.createProducer(destination);
            TextMessage textMessage = session.createTextMessage();
            textMessage.setText(payload);
            messageProducer.send(textMessage);
        } finally {
            if(connection != null){
                try{
                    connection.close();
                } catch (JMSException ignore){

                }
            }
        }
    }

where

  • SysLogEntry is a class with JAXB annotations, which I use for serialization
  • JMS_CONNECTION_FACTORY_JNDI_NAME is the JNDI name of an XA connection factory

However every time the transaction in which I create the log message is rolled back, the log message is not put to the loggers input queue.

Can somebody tell me what is wrong with my code? I have considered to use a separate stateless session bean which would start a new transaction for log message producing, but there is a disadvantage with this approach: inside an EJB I could easily let the container inject the logger bean, but I have also non EJB objects in which I would like to do asynchronous logging.

My application servier is Weblogic 10.3.3

3

3 Answers

1
votes

In case of a system exception (or sessionContext.setRollbackOnly), the global transaction is rolled back by the container (if you use container managed transactions). That means that any actions on an XA-aware resource enlisted in the global transaction are rolled back.

This includes in your case the pending message which you want to send to your logger. This one gets deleted, because the transaction is rolled back.


In your case it should be enough, if you use a non XA connection factory. This way the message should be sent at once (in case of a logger even helpful). So sending runs in a local transaction spanning the JMS sending task only.

As for non EJB objects: You could extract your sending functionality using a non XA connection factory into a normal Java class (= not an EJB), and use it both from EJBs and normal classes.

1
votes

You could assign a static member of the class that contains the static log method to an instance of the stateless EJB proxy. The proxy itself just routes the call to a free bean from the instance pool, so it can be statically shared.

But... Starting a new transaction just to escape the current transaction sounds wasteful. Especially if logging to this async logger is frequent you might want to do it differently.

Use a simple Java SE executor pool with a few threads and just submit work to that (a Runnable). Inside this runnable you can still send the JMS message a keep your existing code as it is. When a thread from the pool picks up the work, the transactional context is lost, but this is exactly what you needed on the first place.

0
votes

I would create a separate BEAN for logging Message and would annotate the sendMessage to start own and independent transaction:

@Stateless
public class SenderBean  {

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public sendMsgToDestination(String payload, ConnectionFactory connFactory, Destination destination, boolean sessionTransacted, int acknowledgeMode) throws JMSException{
        ...
        Connection connection = null;
        try(
           connection = connFactory.createConnection();
           Session session = connection.createSession(sessionTransacted, acknowledgeMode);
        ){
            MessageProducer messageProducer = session.createProducer(destination);
            TextMessage textMessage = session.createTextMessage();
            textMessage.setText(payload);
            messageProducer.send(textMessage);
        }
    }

Then I would use this BEAN for logging, which will write the messages to the queue even if your caller transaction is rolled back