Java Spring集成:持久性和事务性队列通道

Java Spring集成:持久性和事务性队列通道,java,spring,spring-integration,Java,Spring,Spring Integration,在Spring Integration中,我们有一个设置,如下所示: ---> ---> (dispatcher) Messages --> Gateway ----> QueueChannel ---> MessageHandler (worker)

在Spring Integration中,我们有一个设置,如下所示:

                                                     ---> 
                                                     ---> 
(dispatcher) Messages --> Gateway ----> QueueChannel ---> MessageHandler (worker)
                                                     ---> 
                                                     ---> 
因此,我们有一个调度程序线程,它从MQTT代理接收消息并将它们转发到队列中。队列的轮询器提供了TaskExecuter,因此使用者是多线程的。 我们设法实现了所有的功能。因此,刚才描述的设置已经实现

现在为了保证不丢失数据我们想做两件事:

1.: 我们希望队列能够持久化数据,因此当程序意外关闭时,队列中的所有数据仍然存在。 这也对我们有用,我们使用MongoDB作为数据库,因为我们在您的文档中读到,这是推荐的方法

2.: 我们要确保的第二件事是工作线程是事务性的。因此,只有当工作线程正确返回时,消息才会从队列中被永久删除(因此也将从持久消息存储中删除)。如果程序在处理消息期间(由工作线程)关闭,则消息在下次启动时仍将在队列中。 此外,例如,如果工作进程在消息处理过程中出现异常,它将被放回队列

我们的实施:

如前所述,程序的基本设置已经实现。然后,我们使用队列的消息存储实现扩展了基本实现

队列通道:

由Messagestore支持:

匹配的轮询器:

执行人:

我们尝试了什么? 正如现在所解释的,我们希望用事务功能扩展这个实现。我们尝试使用setTransactionSynchronizationFactory,如解释的那样,但它不起作用(没有出现错误或其他任何情况,但行为仍与添加TransactionSynchronizer之前一样):

在spring集成中实现我们的需求的最佳方式是什么

编辑: 根据答案中的建议,我选择使用JdbcChannelMessageStore执行此操作。因此,我尝试将上述XML实现(18.4.2)转换为Java。我不太确定该怎么做,这是我迄今为止尝试过的:

我创建了H2数据库并运行上面显示的脚本

已创建JDBCChannelMessageStore Bean:

@Bean
public JdbcChannelMessageStore store() {
    JdbcChannelMessageStore ms =  new JdbcChannelMessageStore();
    ms.setChannelMessageStoreQueryProvider(queryProvider());
    ms.setUsingIdCache(true);
    ms.setDataSource(dataSource);
    return ms;
}
已创建一个ChannelMessageStoreQueryProvider

    @Bean
    public ChannelMessageStoreQueryProvider queryProvider() {
        return new H2ChannelMessageStoreQueryProvider();
    }
调整民意测验:

@Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() throws Exception {
    PollerMetadata poll = Pollers.fixedDelay(10).get();
    poll.setTaskExecutor(consumer);
    poll.setAdviceChain(Collections.singletonList(transactionInterceptor()));

    return poll;
}
自动连线我的PlaatformTransactionManager:

@Autowired
PlatformTransactionManager transactionManager;
@Bean
public TransactionInterceptor transactionInterceptor() {
    return new TransactionInterceptorBuilder(true)
                .transactionManager(transactionManager)
                .isolation(Isolation.READ_COMMITTED)
                .propagation(Propagation.REQUIRED)
                .build();
}
并从TransactionManager创建TransactionInterceptor:

@Autowired
PlatformTransactionManager transactionManager;
@Bean
public TransactionInterceptor transactionInterceptor() {
    return new TransactionInterceptorBuilder(true)
                .transactionManager(transactionManager)
                .isolation(Isolation.READ_COMMITTED)
                .propagation(Propagation.REQUIRED)
                .build();
}

如果您需要将队列作为事务性队列,那么您肯定应该查看事务性
MessageStore
。只有JDBC一个是这样的。只是因为只有JDBC支持事务。因此,当我们执行
删除
时,只有在发送已提交时才可以

MongoDB或任何其他NoSQL数据库都支持这种模型,因此您只能在回滚时使用
TransactionSynchronizationFactory
将失败的消息推送到DB

更新

@RunWith(SpringRunner.class)
@DirtiesContext
public class So47264688Tests {

    private static final String MESSAGE_GROUP = "transactionalQueueChannel";

    private static EmbeddedDatabase dataSource;

    @BeforeClass
    public static void init() {
        dataSource = new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("classpath:/org/springframework/integration/jdbc/schema-drop-h2.sql")
                .addScript("classpath:/org/springframework/integration/jdbc/schema-h2.sql")
                .build();
    }

    @AfterClass
    public static void destroy() {
        dataSource.shutdown();
    }

    @Autowired
    private PollableChannel transactionalQueueChannel;

    @Autowired
    private JdbcChannelMessageStore jdbcChannelMessageStore;

    @Autowired
    private PollingConsumer serviceActivatorEndpoint;

    @Autowired
    private CountDownLatch exceptionLatch;

    @Test
    public void testTransactionalQueueChannel() throws InterruptedException {
        GenericMessage<String> message = new GenericMessage<>("foo");
        this.transactionalQueueChannel.send(message);

        assertTrue(this.exceptionLatch.await(10, TimeUnit.SECONDS));
        this.serviceActivatorEndpoint.stop();

        assertEquals(1, this.jdbcChannelMessageStore.messageGroupSize(MESSAGE_GROUP));
        Message<?> messageFromStore = this.jdbcChannelMessageStore.pollMessageFromGroup(MESSAGE_GROUP);

        assertNotNull(messageFromStore);
        assertEquals(message, messageFromStore);
    }

    @Configuration
    @EnableIntegration
    public static class ContextConfiguration {

        @Bean
        public PlatformTransactionManager transactionManager() {
            return new DataSourceTransactionManager(dataSource);
        }

        @Bean
        public ChannelMessageStoreQueryProvider queryProvider() {
            return new H2ChannelMessageStoreQueryProvider();
        }

        @Bean
        public JdbcChannelMessageStore jdbcChannelMessageStore() {
            JdbcChannelMessageStore jdbcChannelMessageStore = new JdbcChannelMessageStore(dataSource);
            jdbcChannelMessageStore.setChannelMessageStoreQueryProvider(queryProvider());
            return jdbcChannelMessageStore;
        }

        @Bean
        public PollableChannel transactionalQueueChannel() {
            return new QueueChannel(new MessageGroupQueue(jdbcChannelMessageStore(), MESSAGE_GROUP));
        }

        @Bean
        public TransactionInterceptor transactionInterceptor() {
            return new TransactionInterceptorBuilder()
                    .transactionManager(transactionManager())
                    .isolation(Isolation.READ_COMMITTED)
                    .propagation(Propagation.REQUIRED)
                    .build();
        }

        @Bean
        public TaskExecutor threadPoolTaskExecutor() {
            ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
            threadPoolTaskExecutor.setCorePoolSize(5);
            return threadPoolTaskExecutor;
        }

        @Bean(name = PollerMetadata.DEFAULT_POLLER)
        public PollerMetadata poller() {
            return Pollers.fixedDelay(10)
                    .advice(transactionInterceptor())
                    .taskExecutor(threadPoolTaskExecutor())
                    .get();
        }

        @Bean
        public CountDownLatch exceptionLatch() {
            return new CountDownLatch(2);
        }

        @ServiceActivator(inputChannel = "transactionalQueueChannel")
        public void handle(Message<?> message) {
            System.out.println(message);
            try {
                throw new RuntimeException("Intentional for rollback");
            }
            finally {
                exceptionLatch().countDown();
            }
        }

    }

}
@RunWith(SpringRunner.class)
@肮脏的环境
公共类SO47264688测试{
私有静态最终字符串消息\u GROUP=“transactionalQueueChannel”;
私有静态嵌入式数据库数据源;
@课前
公共静态void init(){
dataSource=新的EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript(“classpath:/org/springframework/integration/jdbc/schema-drop-h2.sql”)
.addScript(“classpath:/org/springframework/integration/jdbc/schema-h2.sql”)
.build();
}
@下课
公共静态无效销毁(){
dataSource.shutdown();
}
@自动连线
专用PollableChannel transactionalQueueChannel;
@自动连线
私有JdbcChannelMessageStore JdbcChannelMessageStore;
@自动连线
私有轮询消费者服务激活端点;
@自动连线
私人倒计时闩锁例外情况;
@试验
public void testTransactionalQueueChannel()引发InterruptedException{
GenericMessage message=新的GenericMessage(“foo”);
this.transactionalQueueChannel.send(消息);
assertTrue(此.exceptionLatch.await(10,TimeUnit.SECONDS));
this.serviceActivationEndpoint.stop();
assertEquals(1,this.jdbcChannelMessageStore.messageGroupSize(MESSAGE_GROUP));
Message messageFromStore=this.jdbcChannelMessageStore.pollMessageFromGroup(消息组);
assertNotNull(messageFromStore);
assertEquals(message,messageFromStore);
}
@配置
@使能集成
公共静态类上下文配置{
@豆子
公共平台transactionManager transactionManager(){
返回新的数据源TransactionManager(数据源);
}
@豆子
公共频道MessageStoreQueryProvider queryProvider(){
返回新的H2ChannelMessageStoreQueryProvider();
}
@豆子
公共JdbcChannelMessageStore JdbcChannelMessageStore(){
JdbcChannelMessageStore JdbcChannelMessageStore=新的JdbcChannelMessageStore(数据源);
setChannelMessageStoreQueryProvider(queryProvider());
返回jdbcChannelMessageStore;
}
@豆子
public PollableChannel transactionalQueueChannel(){
返回新的QueueChannel(新的MessageGroupQueue(jdbcChannelMessageStore(),MESSAGE_GROUP));
}
@豆子
公共事务接收器TransactionInterceptor(){
返回新的TransactionInterceptorBuilder()
.transactionManager(transactionManager())
.isolation(隔离.READ_提交)
.传播(需要传播)
.build();
@Autowired
PlatformTransactionManager transactionManager;
@Bean
public TransactionInterceptor transactionInterceptor() {
    return new TransactionInterceptorBuilder(true)
                .transactionManager(transactionManager)
                .isolation(Isolation.READ_COMMITTED)
                .propagation(Propagation.REQUIRED)
                .build();
}
@RunWith(SpringRunner.class)
@DirtiesContext
public class So47264688Tests {

    private static final String MESSAGE_GROUP = "transactionalQueueChannel";

    private static EmbeddedDatabase dataSource;

    @BeforeClass
    public static void init() {
        dataSource = new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("classpath:/org/springframework/integration/jdbc/schema-drop-h2.sql")
                .addScript("classpath:/org/springframework/integration/jdbc/schema-h2.sql")
                .build();
    }

    @AfterClass
    public static void destroy() {
        dataSource.shutdown();
    }

    @Autowired
    private PollableChannel transactionalQueueChannel;

    @Autowired
    private JdbcChannelMessageStore jdbcChannelMessageStore;

    @Autowired
    private PollingConsumer serviceActivatorEndpoint;

    @Autowired
    private CountDownLatch exceptionLatch;

    @Test
    public void testTransactionalQueueChannel() throws InterruptedException {
        GenericMessage<String> message = new GenericMessage<>("foo");
        this.transactionalQueueChannel.send(message);

        assertTrue(this.exceptionLatch.await(10, TimeUnit.SECONDS));
        this.serviceActivatorEndpoint.stop();

        assertEquals(1, this.jdbcChannelMessageStore.messageGroupSize(MESSAGE_GROUP));
        Message<?> messageFromStore = this.jdbcChannelMessageStore.pollMessageFromGroup(MESSAGE_GROUP);

        assertNotNull(messageFromStore);
        assertEquals(message, messageFromStore);
    }

    @Configuration
    @EnableIntegration
    public static class ContextConfiguration {

        @Bean
        public PlatformTransactionManager transactionManager() {
            return new DataSourceTransactionManager(dataSource);
        }

        @Bean
        public ChannelMessageStoreQueryProvider queryProvider() {
            return new H2ChannelMessageStoreQueryProvider();
        }

        @Bean
        public JdbcChannelMessageStore jdbcChannelMessageStore() {
            JdbcChannelMessageStore jdbcChannelMessageStore = new JdbcChannelMessageStore(dataSource);
            jdbcChannelMessageStore.setChannelMessageStoreQueryProvider(queryProvider());
            return jdbcChannelMessageStore;
        }

        @Bean
        public PollableChannel transactionalQueueChannel() {
            return new QueueChannel(new MessageGroupQueue(jdbcChannelMessageStore(), MESSAGE_GROUP));
        }

        @Bean
        public TransactionInterceptor transactionInterceptor() {
            return new TransactionInterceptorBuilder()
                    .transactionManager(transactionManager())
                    .isolation(Isolation.READ_COMMITTED)
                    .propagation(Propagation.REQUIRED)
                    .build();
        }

        @Bean
        public TaskExecutor threadPoolTaskExecutor() {
            ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
            threadPoolTaskExecutor.setCorePoolSize(5);
            return threadPoolTaskExecutor;
        }

        @Bean(name = PollerMetadata.DEFAULT_POLLER)
        public PollerMetadata poller() {
            return Pollers.fixedDelay(10)
                    .advice(transactionInterceptor())
                    .taskExecutor(threadPoolTaskExecutor())
                    .get();
        }

        @Bean
        public CountDownLatch exceptionLatch() {
            return new CountDownLatch(2);
        }

        @ServiceActivator(inputChannel = "transactionalQueueChannel")
        public void handle(Message<?> message) {
            System.out.println(message);
            try {
                throw new RuntimeException("Intentional for rollback");
            }
            finally {
                exceptionLatch().countDown();
            }
        }

    }

}