【问题标题】:JMS doesn't rollback XA transaction (or doesn't participate in one)JMS 不回滚 XA 事务(或不参与其中)
【发布时间】:2014-04-30 02:34:14
【问题描述】:

我对 XA 事务比较陌生。几天来,我一直在努力使一个简单的 XA 事务工作无济于事。

首先,我尝试使用两个不同的数据库。我设置了 2 个 XA 数据源,并在第二个数据库操作失败时成功回滚了第一个数据库操作。到目前为止,一切都很好。但是后来我尝试用 JMS connectionFactory 替换第二个数据源,并且无法重现相同的行为。

以下是相关代码:

数据库逻辑

@Stateless
public class FirstDB implements FirstDBLocal {

    @PersistenceContext(unitName = "xaunit")
    private EntityManager em;

    public void doSomething() {
        SomeEntity someEntity = em.find(SomeEntity.class, 1234L);
        someEntity.setSomeFlag(false);
    }

}

JMS 代码

@Stateless
public class SecondJMS implements SecondJMSLocal {

    @Resource(mappedName = "java:/JmsXA")
    private ConnectionFactory connFactory;

    @Resource(mappedName = "queue/Some.Queue")
    private Queue q;

    @Override
    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    public void sendMsg() {
        Session session = null;
        Connection conn = null;
        MessageProducer producer = null;
        try {
            conn = connFactory.createConnection("guest", "guest");

            session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);

            producer = session.createProducer(q);

            // Not sure if I need this, but I found it in the sample code
            conn.start();

            TextMessage tm = session.createTextMessage(new Date().toString());
            producer.send(tm);

            throw new RuntimeException("Fake exception");
        } catch (JMSException e) {
            e.printStackTrace();
        } catch (RuntimeException e) {
            e.printStackTrace();
        } finally {
            // close all resources
        }
    }

}

胶水代码

@Stateless
public class TestDBandJMS implements TestDBandJMSLocal {

    @EJB
    private FirstDBLocal firstDBLocal;

    @EJB
    private SecondJMSLocal secondJMSLocal;

    public void doStuff() {
        firstDBLocal.doSomething();
        secondJMSLocal.sendMsg();
    }

}

XA 连接工厂配置(一切都是 JBoss 默认的,除了注释掉的安全设置):

<tx-connection-factory>
      <jndi-name>JmsXA</jndi-name>
      <xa-transaction/>
      <rar-name>jms-ra.rar</rar-name>
      <connection-definition>org.jboss.resource.adapter.jms.JmsConnectionFactory</connection-definition>
      <config-property name="SessionDefaultType" type="java.lang.String">javax.jms.Topic</config-property>
      <config-property name="JmsProviderAdapterJNDI" type="java.lang.String">java:/DefaultJMSProvider</config-property>
      <max-pool-size>20</max-pool-size>
      <!-- <security-domain-and-application>JmsXARealm</security-domain-and-application> -->
      <depends>jboss.messaging:service=ServerPeer</depends>
   </tx-connection-factory>

我也有非常简单的 MDB,它只是将收到的消息打印到控制台(不打算发布代码,因为它很简单)。

问题是,当 JMS 代码中抛出异常时,MDB 仍然接收到消息,并且 SomeEntity 在数据库代码中成功更新(而我希望它回滚)。

这里是JMS log。我在那里看到的一件可疑的事情是:

received ONE_PHASE_COMMIT request

就像我说的,我对 XA 还不太熟悉,但我希望在这里看到 TWO_PHASE_COMMIT,因为应该有 2 个资源参与活动事务。

任何帮助将不胜感激。

更新

在我尝试了@djmorton 的建议后,它最终奏效了。 使用 JBoss 5.1 时要记住的另一件重要事情是 XA JMS ConnectionFactory 的查找名称是“java:/JmsXA”。我尝试了同样的方法

@Resource(mappedName = "XAConnectionFactory")
private ConnectionFactory connFactory;

它没有用。

【问题讨论】:

    标签: jms jboss5.x jta xa jpa-1.0


    【解决方案1】:

    在将 RuntimeException 抛出到 sendMsg() 方法后,您正在捕获它。异常不会触发事务回滚,除非它被抛出堆栈。使用容器管理的事务时,容器会在方法调用中添加拦截器,以设置事务并在引发未经检查的异常时处理回滚。如果异常没有从方法中抛出,则拦截器不知道它需要将事务返回给角色。

    编辑 1:

    请注意,只有 RuntimeException 或 RuntimeException 的子类被抛出才会导致事务回滚。已检查的异常(扩展 Exception 而不是 RuntimeException 的异常)不会导致回滚,除非使用 @ApplicationException(rollback=true) 进行注释。

    另一种选择是注入一个 EJBContext 对象,并在方法超出范围时调用 .setRollbackOnly() 来强制事务回滚:

    @Stateless
    public class SomeEjb {    
        @Resource
        private EJBContext context;
    
        @TransactionAttribute(TransactionAttributeType.MANDATORY)
        public void rollMeBack() {
            context.setRollbackOnly();
        }
    }
    

    【讨论】:

    • EJB 事务不是在任何异常发生时就标记为回滚吗?例如,在处理异常部分的this article中明确指出,一旦抛出异常,事务就会被标记为回滚,即使在catch 子句。 ONE_PHASE_COMMIT 消息对您来说是不是很可疑?
    • 没有。仅当事务方法抛出 RuntimeException(而不仅仅是异常)或在 UserTransaction 对象上调用 .setRollbackOnly() 时,才会回滚 EJB 事务。抛出异常的行为并不是导致事务回滚的原因,而是将该异常传递给容器在调用 EJB 服务方法之前调用的事务拦截器,因此实际上必须将异常向上传递到堆栈中。
    • 感谢您的回复。您如何看待这条 ONE_PHASE_COMMIT 消息?这让我怀疑 JMS Session 没有参与活动的 XA 事务。在接受答案之前,我必须玩一会儿。
    • 这条信息确实有点奇怪。您的代码显示您正在使用 JmsXA 连接工厂...您确定您使用的是正确配置的 XA 数据源,包括正确的 XA 驱动程序类名吗?你用的是什么版本的 JBoss?
    • 然后稍微修改一下你的测试......不要在你的消息生产者中抛出异常。先调用你的发送方法,然后调用你的数据库修改方法。但是,向您的表添加一个约束,当您提交数据库更改时该约束将失败。数据库异常应该触发事务的回滚。如果您看到消息被发送到您的 MDB,那么您就知道您的消息生产者没有参与 XA 事务。如果消息没有被发送,则它成功参与了交易。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-05
    • 1970-01-01
    • 1970-01-01
    • 2015-01-20
    • 2011-09-07
    • 1970-01-01
    相关资源
    最近更新 更多