使用Spring,JOOQ,Postgres和ActiveMQ时没有回滚

时间:2015-09-26 12:16:12

标签: spring postgresql activemq jooq atomikos

数据库写入没有像我预期的那样回滚。 我花了很多时间阅读软件文档和网络发布。 我无法解决这个问题。

我希望大家能帮助我。

方案

  • 我的应用程序从队列中提取消息,从中提取数据 消息,并将其写入数据库。
  • 写入数据库的方法执行2次SQL插入。
  • 第二个插入会出现异常:org.postgresql.util.PSQLException:错误:重复键值违反了唯一约束" table2_PK"
  • 但是,第一个插入仍然会被提交到数据库。

相关软件

  1. spring-boot 1.2.5.RELEASE
  2. atomikos-util 3.9.3(来自spring-boot-starter-jta-atomikos 1.2.5.RELEASE)
  3. jooq 3.6.2
  4. postgresql 9.4-1201-jdbc41
  5. activemq-client 5.1.2
  6. 应用程序代码 - 我已粘贴下面代码的相关部分。

    1. GdmServer - 我的"服务器" class,也声明了Spring bean 构造
    2. PortSIQueue - 我的JMS MessageListener类
    3. 内核 - 我的工作类,即写入数据库的代码,我的MessageListener调用的Spring bean
    4. 我很感激任何人都可以提供帮助。

      由于

      package com.sm.gis.gdm;
      
      import javax.transaction.SystemException;
      import javax.transaction.UserTransaction;
      
      import org.apache.activemq.ActiveMQXAConnectionFactory;
      import org.jooq.DSLContext;
      import org.jooq.SQLDialect;
      import org.jooq.impl.DSL;
      import org.postgresql.xa.PGXADataSource;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
      import org.springframework.context.ConfigurableApplicationContext;
      import org.springframework.context.annotation.Bean;
      import org.springframework.jms.annotation.EnableJms;
      import org.springframework.jms.core.JmsTemplate;
      import org.springframework.jms.listener.DefaultMessageListenerContainer;
      import org.springframework.transaction.PlatformTransactionManager;
      import org.springframework.transaction.annotation.EnableTransactionManagement;
      import org.springframework.transaction.jta.JtaTransactionManager;
      
      import com.atomikos.icatch.jta.UserTransactionImp;
      import com.atomikos.icatch.jta.UserTransactionManager;
      import com.atomikos.jms.AtomikosConnectionFactoryBean;
      import com.sm.gis.config.GisConfig;
      
      @SpringBootApplication
      @EnableJms
      @EnableTransactionManagement
      public class GdmServer {
      
          @Autowired
          ConfigurableApplicationContext  context;
          @Autowired
          GisConfig                       gisConfig;
      
          /**
           * Starts the GDM Server
           */
          public static void main(String[] args) {
              SpringApplication.run(GdmServer.class, args);
          }
      
          // -------------------------------------------------------------------------
          // Spring bean configurations
          // -------------------------------------------------------------------------
      
          @Bean
          GisConfig gisConfig() {
              return new GisConfig();
          }
      
          @Bean
          PlatformTransactionManager transactionManager() throws SystemException {
              JtaTransactionManager manager = new JtaTransactionManager();
              manager.setTransactionManager( atomikosUserTransactionManager() );
              manager.setUserTransaction   ( atomikosUserTransaction() );
              manager.setAllowCustomIsolationLevels(true);
              return manager;
          }
      
          @Bean(initMethod = "init", destroyMethod = "close")
          UserTransactionManager atomikosUserTransactionManager() throws SystemException {
              UserTransactionManager manager = new UserTransactionManager();
              manager.setStartupTransactionService(true);
              manager.setForceShutdown(false);
              manager.setTransactionTimeout( gisConfig.getTxnTimeout() );
              return manager;
          }
      
          @Bean
          UserTransaction atomikosUserTransaction() {
              return new UserTransactionImp();
          }
      
          @Bean(initMethod = "init", destroyMethod = "close")
          AtomikosDataSourceBean atomikosJdbcConnectionFactory() {
              PGXADataSource pgXADataSource = new PGXADataSource();
              pgXADataSource.setUrl( gisConfig.getGdbUrl() );
              pgXADataSource.setUser( gisConfig.getGdbUser() );
              pgXADataSource.setPassword( gisConfig.getGdbPassword() );
      
              AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
              xaDataSource.setXaDataSource(pgXADataSource);
              xaDataSource.setUniqueResourceName("gdb");
              xaDataSource.setPoolSize( gisConfig.getGdbPoolSize() );
              return xaDataSource;
          }
      
          @Bean
          DSLContext dslContext() {
              DSLContext dslContext = DSL.using(atomikosJdbcConnectionFactory(), SQLDialect.POSTGRES);
              return dslContext;
          }
      
          @Bean(initMethod = "init", destroyMethod = "close")
          AtomikosConnectionFactoryBean atomikosJmsConnectionFactory() {
              ActiveMQXAConnectionFactory activeMQXAConnectionFactory = new ActiveMQXAConnectionFactory();
              activeMQXAConnectionFactory.setBrokerURL( gisConfig.getMomBrokerUrl() );
      
              AtomikosConnectionFactoryBean atomikosConnectionFactoryBean = new AtomikosConnectionFactoryBean();
              atomikosConnectionFactoryBean.setUniqueResourceName("activeMQBroker");
              atomikosConnectionFactoryBean.setXaConnectionFactory(activeMQXAConnectionFactory);
              atomikosConnectionFactoryBean.setLocalTransactionMode(false);
              return atomikosConnectionFactoryBean;
          }
      
          @Bean
          DefaultMessageListenerContainer queueWrapperGDM() throws SystemException {
              DefaultMessageListenerContainer messageSource = new DefaultMessageListenerContainer();
              messageSource.setTransactionManager( transactionManager() );
              messageSource.setConnectionFactory( atomikosJmsConnectionFactory() );
              messageSource.setSessionTransacted(true);
              messageSource.setConcurrentConsumers(1);
              messageSource.setReceiveTimeout( gisConfig.getMomQueueGdmTimeoutReceive() );
              messageSource.setDestinationName( gisConfig.getMomQueueGdmName() );
              messageSource.setMessageListener( context.getBean("portSIQueue") );
              return messageSource;
          }
      
          @Bean
          JmsTemplate queueWrapperLIMS() {
              JmsTemplate jmsTemplate = new JmsTemplate();
              jmsTemplate.setConnectionFactory( atomikosJmsConnectionFactory() );
              jmsTemplate.setDefaultDestinationName( gisConfig.getMomQueueLimsName() );
              jmsTemplate.setSessionTransacted(true);
              return jmsTemplate;
          }
      
      }
      
      package com.sm.gis.gdm.ports;
      
      import javax.jms.JMSException;
      import javax.jms.Message;
      import javax.jms.MessageListener;
      import javax.jms.TextMessage;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.ConfigurableApplicationContext;
      import org.springframework.stereotype.Component;
      import org.springframework.transaction.annotation.Transactional;
      
      import com.sm.gis.gdm.kernel.Kernel;
      import com.sm.gis.sdo.xml.marshaler.GisMessageMarshaler;
      import com.sm.gis.sdo.xml.service.message.CreateGenomicTestOrderInGIS;
      
      @Component
      public class PortSIQueue implements MessageListener {
      
          @Autowired
          ConfigurableApplicationContext  context;
          @Autowired
          GisMessageMarshaler             queueMessageMashaler;
          @Autowired
          Kernel                          kernel;
      
          @Override
          @Transactional(rollbackFor = {Throwable.class})
          public void onMessage(Message jmsMessage) {
      
              TextMessage jmsTextMessage = (TextMessage) jmsMessage;
      
              // Extract JMS message body...
              String jmsPayload = "";
              try {
                  jmsPayload = jmsTextMessage.getText();
              } catch (JMSException e) {
                  throw new RuntimeException(e);
              }
      
              // Marshal XML text to object...
              Object gisMessage = queueMessageMashaler.toObject(jmsPayload);
      
              kernel.receiveCreateGenomicTestOrderInGIS( (CreateGenomicTestOrderInGIS) gisMessage );
          }
      
      }
      
      package com.sm.gis.gdm.kernel;
      
      import org.jooq.DSLContext;
      import org.jooq.impl.DSL;
      
      @Component
      public class Kernel {
      
          @Autowired
          ConfigurableApplicationContext  context;
          @Autowired
          DSLContext                      dslContext;
      
      <snip>
          public void receiveCreateGenomicTestOrderInGIS(CreateGenomicTestOrderInGIS message) {
      
                  dslContext.insertInto(table1)
                      .set(...)
                      .set(...)
                  .execute();
      
                  dslContext.insertInto(table2)
                      .set(...)
                      .set(...)
                  .execute();
          }
      <snip>
      }
      

2 个答案:

答案 0 :(得分:1)

我是个白痴。 事实证明这个问题是由于我的应用程序逻辑存在缺陷。 如果第一次处理消息的尝试失败并且异常,则ActiveMQ组件将重试消息。为第一次尝试创建的事务正确回滚。这是第二次成功的尝试。重试成功,因为数据库序列号在第一次尝试期间由应用程序逻辑递增,而第二次尝试不会导致重复的键冲突。在纠正应用程序逻辑缺陷之后,因为在我的应用程序中无论如何都没有重试消息,我也关闭了重试。 我为浪费那些阅读我帖子的人的时间而道歉。

在此过程中,我确实对实现进行了一些更改。这些更改使某些默认值显式选择。我保留了这些更改,因为我相信他们会让我的团队中的其他开发人员更容易理解更快发生的事情。我还保留了JOOQ异常翻译代码,因为在其他情况下它是需要的,并且似乎是最佳实践。

我已在此帖中包含修改后的代码,以防其他人认为有用。

package com.sm.gis.gdm;

import javax.transaction.SystemException;
import javax.transaction.UserTransaction;

import org.apache.activemq.ActiveMQXAConnectionFactory;
import org.apache.activemq.RedeliveryPolicy;
import org.jooq.DSLContext;
import org.jooq.SQLDialect;
import org.jooq.impl.DefaultConfiguration;
import org.jooq.impl.DefaultDSLContext;
import org.jooq.impl.DefaultExecuteListenerProvider;
import org.postgresql.xa.PGXADataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jms.AtomikosConnectionFactoryBean;
import com.sm.gis.config.GisConfig;

@SpringBootApplication
@EnableJms
@EnableTransactionManagement(proxyTargetClass=true)
public class GdmServer {

    @Autowired
    ConfigurableApplicationContext   context;
    @Autowired
    GisConfig                        gisConfig;

    /**
     * Starts the GDM Server
     */
    public static void main(String[] args) {
        SpringApplication.run(GdmServer.class, args);
    }

    // -------------------------------------------------------------------------
    // Spring bean configurations
    // -------------------------------------------------------------------------

    @Bean
    GisConfig gisConfig() {
        return new GisConfig();
    }

    @Bean
    @DependsOn({ "atomikosUserTransactionManager", "atomikosUserTransaction", "atomikosJdbcConnectionFactory", "atomikosJmsConnectionFactory" })
    PlatformTransactionManager transactionManager() throws SystemException {
        JtaTransactionManager manager = new JtaTransactionManager();
        manager.setTransactionManager( atomikosUserTransactionManager() );
        manager.setUserTransaction( atomikosUserTransaction() );
        manager.setAllowCustomIsolationLevels(true);
        manager.afterPropertiesSet();
        return manager;
    }

    @Bean(initMethod = "init", destroyMethod = "close")
    UserTransactionManager atomikosUserTransactionManager() throws SystemException {
        UserTransactionManager manager = new UserTransactionManager();
        manager.setStartupTransactionService(true);
        manager.setForceShutdown(false);
        manager.setTransactionTimeout( gisConfig.getTxnTimeout() );
        return manager;
    }

    @Bean
    UserTransaction atomikosUserTransaction() {
        return new UserTransactionImp();
    }

    @Bean(initMethod = "init", destroyMethod = "close")
    AtomikosDataSourceBean atomikosJdbcConnectionFactory() throws Exception {
        PGXADataSource pgXADataSource = new PGXADataSource();
        pgXADataSource.setUrl( gisConfig.getGdbUrl() );
        pgXADataSource.setUser( gisConfig.getGdbUser() );
        pgXADataSource.setPassword( gisConfig.getGdbPassword() );

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(pgXADataSource);
        xaDataSource.setUniqueResourceName("gdb");
        xaDataSource.setPoolSize( gisConfig.getGdbPoolSize() );
        xaDataSource.setTestQuery("SELECT 1");
        xaDataSource.afterPropertiesSet();
        return xaDataSource;
    }

    @Bean
    @DependsOn({ "atomikosJdbcConnectionFactory" })
    DSLContext dslContext() throws Exception {
        DefaultConfiguration jooqConfiguration = new DefaultConfiguration();
        jooqConfiguration.set( SQLDialect.POSTGRES_9_4 );
        jooqConfiguration.set( atomikosJdbcConnectionFactory() );
        jooqConfiguration.set( new DefaultExecuteListenerProvider(new JooqToSpringExceptionTransformer()) );
        DSLContext dslContext = new DefaultDSLContext(jooqConfiguration);
        return dslContext;
    }


    @Bean(initMethod = "init", destroyMethod = "close")
    AtomikosConnectionFactoryBean atomikosJmsConnectionFactory() {
        RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
        redeliveryPolicy.setInitialRedeliveryDelay(0);
        redeliveryPolicy.setRedeliveryDelay(0);
        redeliveryPolicy.setUseExponentialBackOff(false);
        redeliveryPolicy.setMaximumRedeliveries(0);

        ActiveMQXAConnectionFactory activeMQXAConnectionFactory = new ActiveMQXAConnectionFactory();
        activeMQXAConnectionFactory.setBrokerURL( gisConfig.getMomBrokerUrl() );
        activeMQXAConnectionFactory.setRedeliveryPolicy(redeliveryPolicy);

        AtomikosConnectionFactoryBean atomikosConnectionFactoryBean = new AtomikosConnectionFactoryBean();
        atomikosConnectionFactoryBean.setUniqueResourceName("activeMQBroker");
        atomikosConnectionFactoryBean.setXaConnectionFactory(activeMQXAConnectionFactory);
        atomikosConnectionFactoryBean.setLocalTransactionMode(false);
        return atomikosConnectionFactoryBean;
    }

    @Bean
    @DependsOn({ "transactionManager" })
    DefaultMessageListenerContainer queueWrapperGDM() throws SystemException {
        DefaultMessageListenerContainer messageSource = new DefaultMessageListenerContainer();
        messageSource.setTransactionManager( transactionManager() );
        messageSource.setConnectionFactory( atomikosJmsConnectionFactory() );
        messageSource.setSessionTransacted(true);
        messageSource.setSessionAcknowledgeMode(0);
        messageSource.setConcurrentConsumers(1);
        messageSource.setReceiveTimeout( gisConfig.getMomQueueGdmTimeoutReceive() );
        messageSource.setDestinationName( gisConfig.getMomQueueGdmName() );
        messageSource.setMessageListener( context.getBean("portSIQueue") );
        messageSource.afterPropertiesSet();
        return messageSource;
    }

    @Bean
    @DependsOn({ "transactionManager" })
    JmsTemplate queueWrapperLIMS() {
        JmsTemplate jmsTemplate = new JmsTemplate();
        jmsTemplate.setConnectionFactory( atomikosJmsConnectionFactory() );
        jmsTemplate.setDefaultDestinationName( gisConfig.getMomQueueLimsName() );
        jmsTemplate.setSessionTransacted(true);
        jmsTemplate.setSessionAcknowledgeMode(0);
        return jmsTemplate;
    }

}
package com.sm.gis.gdm.ports;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.sm.gis.gdm.kernel.Kernel;
import com.sm.gis.sdo.xml.marshaler.GisMessageMarshaler;
import com.sm.gis.sdo.xml.service.message.CreateGenomicTestOrderInGIS;

@Component
public class PortSIQueue implements MessageListener {

    @Autowired
    ConfigurableApplicationContext    context;
    @Autowired
    GisMessageMarshaler               queueMessageMashaler;
    @Autowired
    Kernel                            kernel;

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = {Throwable.class})
    public void onMessage(Message jmsMessage) {

        TextMessage jmsTextMessage = (TextMessage) jmsMessage;

        // Extract JMS message body...
        String jmsPayload = "";
        try {
            jmsPayload = jmsTextMessage.getText();
        } catch (JMSException e) {
            throw new RuntimeException(e);
        }

        // Marshal XML text to object...
        Object gisMessage = queueMessageMashaler.toObject(jmsPayload);

        kernel.receiveCreateGenomicTestOrderInGIS( (CreateGenomicTestOrderInGIS) gisMessage );

}
package com.sm.gis.gdm.kernel;

import org.jooq.DSLContext;

@Component
public class Kernel {

    @Autowired
    ConfigurableApplicationContext  context;
    @Autowired
    DSLContext                      dslContext;

<snip>
    public void receiveCreateGenomicTestOrderInGIS(CreateGenomicTestOrderInGIS message) {

        dslContext.insertInto(table1)
            .set(...)
            .set(...)
        .execute();

        dslContext.insertInto(table2)
            .set(...)
            .set(...)
        .execute();
    }
<snip>
}

答案 1 :(得分:0)

使用Transactional Annotation有类似的问题。必须在try / catch中使用(begin..commit)/ rollback显式处理事务。不是很优雅和重复,但有效。 TransactionContext保存在当前线程中。所以你的begin方法不需要返回ctx对象。可以使用DSLContext.configuration()实例化TransactionContext。

public class DataSourceTransactionProvider实现TransactionProvider {     private final DataSourceTransactionManager txMgr;

@Inject
public DataSourceTransactionProvider(DataSourceTransactionManager transactionManager) {
    this.txMgr = transactionManager;
}

@Override
public void begin(TransactionContext ctx) throws DataAccessException {
    TransactionStatus transactionStatus = txMgr.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_NESTED));
    ctx.transaction(new DBTransaction(transactionStatus));
}

@Override
public void commit(TransactionContext ctx) throws DataAccessException {
    txMgr.commit(((DBTransaction) ctx.transaction()).transactionStatus);
}

@Override
public void rollback(TransactionContext ctx) throws DataAccessException {
    txMgr.rollback(((DBTransaction) ctx.transaction()).transactionStatus);
}

}