JMS 事务问题

Posted

技术标签:

【中文标题】JMS 事务问题【英文标题】:JMS transaction issue 【发布时间】:2011-06-04 19:37:13 【问题描述】:

我对 JMS 和事务有疑问,我不完全理解。我的应用程序有一个 JDBC 资源和两个 JMS 队列。队列的消息生产者是在来自同一个 jms 会话对象的同一个无状态会话 bean 中创建的。我使用队列的方式如下:创建一个实体,并将其 id 作为属性保存在 JMS 消息中并发送到队列。实体的创建和提交到队列发生在同一个事务中。然后,我的消息驱动 bean 通过 JMS 消息中的 ID 从数据库中检索实体并对其进行处理。

代码大致如下:

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在之前(实体创建之后)的日志文件中:

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).

我正在使用带有 JPA 2.0(和 Apache Derby)的 Glassfish v3.1-b35 和 GF 附带的标准 JMS 提供程序。事务设置以及 JMS 资源(嵌入模式)是默认设置。事务是容器管理的。任何想法这里出了什么问题?异常经常发生。

【问题讨论】:

【参考方案1】:

您的问题在于事务管理。一旦您将消息提交到 JMS 目的地,MDB 就会在它自己的事务中提取它。这成为一个问题,因为您提交消息的会话 bean 尚未提交它的事务。现在 MDB 正在尝试加载一个不存在的实体。

将提交代码分离到它自己的类中,并使用 bean 管理的事务。

【讨论】:

如果是这种情况,则会抛出 NullPointerException,因为 MDB 找不到实体。 JMS 在事务提交之后发送,而不是在实际调用 messageProducer.send() 之后发送。 我明白你在说什么,但我不认为这就是它的行为方式。我们遇到了类似的问题,而且与交易有关。 错误“Cannot perform operation PREPARE_TRANSACTION(56) (XAFlag=null) on a transaction in state STARTED(1)”表示第一个事务尚未提交多个线程试图在同一个会话上使用事务而不是同步的。我不能说预期的空指针,但普雷斯顿关于这与事务相关是正确的。 @T.Rob 我也认为这与交易有关。但是 Preston 说“MDB 正在尝试加载一个不存在的实体”。这意味着实体管理器在这种情况下返回 null。但这不是这里发生的事情。 即使实体存在,它也可能被其他事务锁定。您将不得不将这两个进程分开。【参考方案2】:

我已就此联系过 Oracle:他们建议不要在无状态会话 bean 中缓存 JMS 连接。相反,应按需获取连接并立即释放。这样做时没有性能开销,因为连接句柄是实际物理连接的薄包装器(请参阅 Java EE 连接器体系结构规范的第 6.4.3 节)。您也可以参考 glassfish 邮件列表中的this thread 来解决类似问题。

【讨论】:

【参考方案3】:

在回滚实体事务的 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());
        
    


【讨论】:

以上是关于JMS 事务问题的主要内容,如果未能解决你的问题,请参考以下文章

使用 TransactionManager(JMS、数据库)启动多个事务

在事务提交之前传递 JMS 消息

JMS学习五(ActiveMQ的本地事务)

Spring整合JMS——事务管理

学习ActiveMQ:JMS消息的事务管理

ActiveMQ JMS XA Atomikos - 事务未启动错误