Проблема с транзакцией JMS

У меня проблема с JMS и транзакциями, которую я не совсем понимаю. В моем приложении есть один ресурс JDBC и две очереди JMS. Производители сообщений для очередей создаются в одном и том же сеансовом компоненте без сохранения состояния из одного и того же объекта сеанса jms. Я использую очереди следующим образом: создается объект, и его идентификатор сохраняется как свойство в сообщении JMS и отправляется в очередь. Создание объекта и отправка в очередь происходят в одной транзакции. Затем мой bean-компонент, управляемый сообщениями, извлекает объект из базы данных по идентификатору в сообщении JMS и обрабатывает его.

Код выглядит примерно так:

public long doSomething(String message) {
        SomeObject obj = new SomeObject(message);
        entityManager.persist(obj)

       // submit to JMS queue
      try {
            Message jmsMessage = session.createMessage();
            jmsMessage.setLongProperty("id", obj.getId());
            messageProducer.send(jmsMessage);
        } catch (JMSException ex) {
            Logger.getLogger(NotificationQueue.class.getName()).log(Level.SEVERE, null, ex);
        }

       return obj.getId();
}

метод MDB onMessage:

public void onMessage(Message message) {
        Long id;
        try {
            id = message.getLongProperty("id");
        } catch (Exception ex) {
            Logger.getLogger(AlertMessageListener.class.getName()).log(Level.SEVERE, null, ex);
            throw new EJBException(ex);
        }

        SomeObject obj = entityManager.find(SomeObject.class, id);
        obj.process();
 }

Когда объект извлекается из базы данных в методе onMessage (), в файле журнала возникает следующее исключение:

FINE: ENTRY com.test.app.alert.control.AlertMessageListener onMessage
FINE: ENTRY com.test.app.alert.control.MessageDao find
FINER: client acquired: 2104888816
FINER: TX binding to tx mgr, status=STATUS_ACTIVE
FINER: acquire unit of work: 1368213481
FINEST: Execute query ReadObjectQuery(name="readObject" referenceClass=Message sql="SELECT ID, DTYPE, MESSAGE, REPORTTIME, SENDER_USERNAME, ALERTSTATE, TIMERHANDLE, CATEGORY_ID, PRIORITY_PRIOLEVEL FROM MESSAGE WHERE (ID = ?)")
SEVERE: prepareTransaction (XA) on JMSService:jmsdirect failed for connectionId:7979865462417759232 due to Unknown JMSService server error ERROR: com.sun.messaging.jmq.jmsserver.util.BrokerException: Bad transaction state transition. Cannot perform operation PREPARE_TRANSACTION(56) (XAFlag=null) on a transaction in state STARTED(1).
WARNING: JTS5031: Exception [java.lang.RuntimeException: javax.transaction.xa.XAException] on Resource [prepare] operation.
SEVERE: rollbackTransaction (XA) on JMSService:jmsdirect failed for connectionId:7979865462417759232:transactionId=7979865462479908608 due to Unknown JMSService server error ERROR: com.sun.messaging.jmq.jmsserver.util.BrokerException: Bad transaction state transition. Cannot perform operation ROLLBACK_TRANSACTION(48) (XAFlag=null) on a transaction in state STARTED(1).
WARNING: JTS5068: Unexpected error occurred in rollback
javax.transaction.xa.XAException
        at com.sun.messaging.jms.ra.DirectXAResource.rollback(DirectXAResource.java:703)
        at com.sun.jts.jta.TransactionState.rollback(TransactionState.java:193)
        at com.sun.jts.jtsxa.OTSResourceImpl.rollback(OTSResourceImpl.java:333)
        at com.sun.jts.CosTransactions.RegisteredResources.distributeRollback(RegisteredResources.java:1063)
        at com.sun.jts.CosTransactions.TopCoordinator.rollback(TopCoordinator.java:2299)
        at com.sun.jts.CosTransactions.CoordinatorTerm.commit(CoordinatorTerm.java:420)
        at com.sun.jts.CosTransactions.TerminatorImpl.commit(TerminatorImpl.java:250)
        at com.sun.jts.CosTransactions.CurrentImpl.commit(CurrentImpl.java:623)
        at com.sun.jts.jta.TransactionManagerImpl.commit(TransactionManagerImpl.java:319)
        at com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate.commitDistributedTransaction(JavaEETransactionManagerJTSDelegate.java:173)
        at com.sun.enterprise.transaction.JavaEETransactionManagerSimplified.commit(JavaEETransactionManagerSimplified.java:873)
        at com.sun.ejb.containers.BaseContainer.completeNewTx(BaseContainer.java:5115)
        at com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:4880)
        at com.sun.ejb.containers.MessageBeanContainer.afterMessageDeliveryInternal(MessageBeanContainer.java:1207)
        at com.sun.ejb.containers.MessageBeanContainer.afterMessageDelivery(MessageBeanContainer.java:1180)
        at com.sun.ejb.containers.MessageBeanListenerImpl.afterMessageDelivery(MessageBeanListenerImpl.java:86)
        at com.sun.enterprise.connectors.inbound.MessageEndpointInvocationHandler.invoke(MessageEndpointInvocationHandler.java:143)
        at $Proxy260.afterDelivery(Unknown Source)
        at com.sun.messaging.jms.ra.OnMessageRunner.run(OnMessageRunner.java:328)
        at com.sun.enterprise.connectors.work.OneWork.doWork(OneWork.java:114)
        at com.sun.corba.ee.impl.orbutil.threadpool.ThreadPoolImpl$WorkerThread.performWork(ThreadPoolImpl.java:496)
        at com.sun.corba.ee.impl.orbutil.threadpool.ThreadPoolImpl$WorkerThread.run(ThreadPoolImpl.java:537)
Caused by: com.sun.messaging.jmq.jmsservice.JMSServiceException: rollbackTransaction: rollback transaction failed. Connection ID: 7979865462417759232, Transaction ID: 7979865462479908608, XID: null
        at com.sun.messaging.jmq.jmsserver.service.imq.IMQDirectService.rollbackTransaction(IMQDirectService.java:1827)
        at com.sun.messaging.jms.ra.DirectXAResource.rollback(DirectXAResource.java:672)
        ... 21 more
Caused by: com.sun.messaging.jmq.jmsserver.util.BrokerException: Bad transaction state transition. Cannot perform operation ROLLBACK_TRANSACTION(48) (XAFlag=null) on a transaction in state STARTED(1).
        at com.sun.messaging.jmq.jmsserver.data.TransactionState.nextState(TransactionState.java:449)
        at com.sun.messaging.jmq.jmsserver.data.handlers.TransactionHandler.preRollback(TransactionHandler.java:1586)
        at com.sun.messaging.jmq.jmsserver.data.protocol.ProtocolImpl.rollbackTransaction(ProtocolImpl.java:777)
        at com.sun.messaging.jmq.jmsserver.service.imq.IMQDirectService.rollbackTransaction(IMQDirectService.java:1816)
        ... 22 more

FINER: TX afterCompletion callback, status=ROLLEDBACK
FINER: release unit of work
FINER: client released
FINEST: Register the existing object com.test.app.alert.entity.AlertMessage@7981d22
FINER: end unit of work commit
FINEST: Register the existing object Mailserver
FINEST: Register the existing object Low
FINEST: Register the existing object u0 u0 (u0)
FINEST: Register the existing object Sankt Augustin
FINE: RETURN com.test.app.alert.control.MessageDao find
...
WARNING: javax.ejb.EJBException
javax.ejb.EJBException: Transaction aborted
        at com.sun.ejb.containers.BaseContainer.completeNewTx(BaseContainer.java:5121)
        at com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:4880)
        at com.sun.ejb.containers.MessageBeanContainer.afterMessageDeliveryInternal(MessageBeanContainer.java:1207)
        at com.sun.ejb.containers.MessageBeanContainer.afterMessageDelivery(MessageBeanContainer.java:1180)
        at com.sun.ejb.containers.MessageBeanListenerImpl.afterMessageDelivery(MessageBeanListenerImpl.java:86)
        at com.sun.enterprise.connectors.inbound.MessageEndpointInvocationHandler.invoke(MessageEndpointInvocationHandler.java:143)
        at $Proxy260.afterDelivery(Unknown Source)
        at com.sun.messaging.jms.ra.OnMessageRunner.run(OnMessageRunner.java:328)
        at com.sun.enterprise.connectors.work.OneWork.doWork(OneWork.java:114)
        at com.sun.corba.ee.impl.orbutil.threadpool.ThreadPoolImpl$WorkerThread.performWork(ThreadPoolImpl.java:496)
        at com.sun.corba.ee.impl.orbutil.threadpool.ThreadPoolImpl$WorkerThread.run(ThreadPoolImpl.java:537)
Caused by: javax.transaction.RollbackException
        at com.sun.jts.jta.TransactionManagerImpl.commit(TransactionManagerImpl.java:321)
        at com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate.commitDistributedTransaction(JavaEETransactionManagerJTSDelegate.java:173)
        at com.sun.enterprise.transaction.JavaEETransactionManagerSimplified.commit(JavaEETransactionManagerSimplified.java:873)
        at com.sun.ejb.containers.BaseContainer.completeNewTx(BaseContainer.java:5115)
        ... 10 more

Следующая ИНФОРМАЦИЯ была в файле журнала немного раньше (после создания объекта):

INFO: DXAR:start():Warning:Received diff Xid for open txnId:switching transactionId:
DXAR  Xid=(GlobalTransactionID=[B@4eec2030, BranchQualifier=[B@703557ca) 
DXAR TXid=7979865462479908608
got   Xid=(GlobalTransactionID=[B@3e16c853, BranchQualifier=[B@49b9e0fd) 
got  TXid=7979865462480472064

Что именно означает это исключение:

Bad transaction state transition. Cannot perform operation ROLLBACK_TRANSACTION(48) (XAFlag=null) on a transaction in state STARTED(1).

Я использую Glassfish v3.1-b35 с JPA 2.0 (и Apache Derby) и стандартный поставщик JMS, поставляемый с GF. Настройки транзакций по умолчанию, как и ресурсы JMS (режим EMBEDDED). Транзакции управляются контейнером. Есть идеи, что здесь не так? Исключение случается довольно часто.


person Theo    schedule 06.01.2011    source источник


Ответы (3)


Я связался с Oracle по этому поводу: они рекомендуют не кэшировать соединения JMS в сессионном компоненте без сохранения состояния. Вместо этого соединения следует приобретать по запросу и немедленно освобождать. При этом не возникает дополнительных затрат на производительность, поскольку дескриптор соединения представляет собой тонкую оболочку вокруг фактического физического соединения (см. Раздел 6.4.3 Спецификации архитектуры коннектора Java EE). Вы также можете обратиться к эту ветку в списке рассылки Glassfish по аналогичной проблеме.

person Theo    schedule 10.01.2011

Ваша проблема связана с управлением транзакциями. После того, как вы отправите свое сообщение в место назначения JMS, MDB заберет его в своей собственной транзакции. Это становится проблемой, потому что ваш сеансовый компонент, отправляющий сообщение, еще не зафиксировал транзакцию. Теперь MDB пытается загрузить несуществующий объект.

Разделите код отправки в отдельный класс и используйте транзакцию, управляемую компонентом.

person Preston    schedule 06.01.2011
comment
Если бы это было так, то было бы выброшено исключение NullPointerException, потому что MDB не смог найти объект. JMS отправляется после подтверждения транзакции, а НЕ после фактического вызова messageProducer.send (). - person Theo; 07.01.2011
comment
Я понимаю, о чем вы говорите, но не думаю, что он так себя ведет. У нас была аналогичная проблема, и она была связана с транзакцией. - person Preston; 07.01.2011
comment
Ошибка Невозможно выполнить операцию PREPARE_TRANSACTION (56) (XAFlag = null) для транзакции в состоянии STARTED (1) указывает, что первая транзакция не зафиксирована или, что несколько потоков пытаются использовать транзакции на одном и том же сеанс и не синхронизирован. Я не могу говорить с ожидаемым нулевым указателем, но Престон прав в том, что это связано с транзакцией. - person T.Rob; 07.01.2011
comment
@ T.Rob Я тоже думаю, что это связано с транзакцией. Но Престон говорит, что MDB пытается загрузить несуществующий объект. Это означает, что в этом случае диспетчер сущностей возвращает null. Но это не то, что здесь происходит. - person Theo; 08.01.2011
comment
Даже если объект существует, он может быть заблокирован другой транзакцией. Вам придется разделить два процесса. - person Preston; 09.01.2011

У меня также была эта проблема с JMS, когда транзакции сущностей откатывались. Мне удалось исправить это запросом на новую транзакцию, используя:

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)

На EJB, который отправляет сообщения в очередь.

Итак, мой дизайн выглядит примерно так:

Производитель:

@Stateless
@LocalBean
// Note here that a new transaction is required for this bean
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
public class ProducerBean {

    @Resource(mappedName = "jms/theQueue")
    private Queue theQueue;

    @Inject
    private JMSContext jmsContext;


    private static final Logger logger = LoggerFactory.getLogger(ProducerBean.class);

    public void event(TheEvent theEvent) {
        // Place the message in the Queue
        try {
            jmsContext.createProducer().send(theQueue, theEvent);
            logger.info("send event|eventName:{}", theEvent.getEventName());
        } catch (Exception ex) {
            logger.error("Could not send the event|eventName:{}|error:{}", theEvent.getEventName(), ex.getMessage());
        }
    }
}

А мой потребитель выглядит примерно так:

@MessageDriven(name = "TheConsumer", activationConfig = {
    @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "jms/theQueue"),
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
    @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge")
})
public class TheConsumer implements MessageListener {

    private static final Logger logger = LoggerFactory.getLogger(TheConsumer.class);

    @Override
    public void onMessage(Message message) {

        try {
            doSomething();

        } catch (JMSException ex) {
            logger.error("JMSException|could not retrieve the object from the message body - reason: {}", ex.getMessage());
        }
    }

}
person dazito    schedule 17.12.2015