0

I swear I had this working, but when I can back to it after a few months (and an upgrade to Boot 1.5.9), I am having issues.

I set up a JdbcPollingChannelAdapter that I can do a receive() on just fine, but when I put the adapter in a flow that does nothing more than queue the result of the adapter, running .receive on the queue always returns a null (I can see in the console log that the adapter's SQL getting executed, though).

Tests below. Why can I get results from the adapter, but not queue the results? Thank you in advance for any assistance.

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureTestDatabase
@JdbcTest
public class JdbcpollingchanneladapterdemoTests {

  @Autowired
  @Qualifier("dataSource")
  DataSource dataSource;

  private static PollableChannel outputQueue;

    @BeforeClass
    public static void setupClass() {
    outputQueue = MessageChannels.queue().get();
        return;
    }

    @Test
    public void Should_HaveQueue() {
        assertThat(outputQueue, instanceOf(QueueChannel.class));
    }

    @Test
  @Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD,
      statements = "Create Table DEMO (CODE VARCHAR(5));")
  @Sql(executionPhase = ExecutionPhase.AFTER_TEST_METHOD,
      statements = "Drop Table DEMO ;")
    public void Should_Not_HaveMessageOnTheQueue_When_No_DemosAreInTheDatabase() {
        Message<?> message = outputQueue.receive(5000);
        assertThat(message, nullValue()) ;
    }

  @Test
  @Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD,
      statements = "Create Table DEMO (CODE VARCHAR(5));")
  @Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD,
      statements = "Insert into DEMO (CODE) VALUES ('12345');")
  @Sql(executionPhase = ExecutionPhase.AFTER_TEST_METHOD,
      statements = "Drop Table DEMO ;")
  public void Should_HaveMessageOnTheQueue_When_DemosIsInTheDatabase() {
    assertThat(outputQueue, instanceOf(QueueChannel.class));
    Message<?> message = outputQueue.receive(5000);
    assertThat(message, notNullValue());
    assertThat(message.getPayload().toString(), equalTo("15317")) ;
  }

  @Test
  @Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD,
      statements = "Create Table DEMO (CODE VARCHAR(5));")
  @Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD,
      statements = "Insert into DEMO (CODE) VALUES ('12345');")
  @Sql(executionPhase = ExecutionPhase.AFTER_TEST_METHOD,
      statements = "Drop Table DEMO ;")
  public void get_message_directly_from_adapter() {
    JdbcPollingChannelAdapter adapter =
        new JdbcPollingChannelAdapter(dataSource, "SELECT CODE FROM DEMO");
    adapter.setRowMapper(new DemoRowMapper());
    adapter.setMaxRowsPerPoll(1);
    Message<?> message = adapter.receive();
    assertThat(message, notNullValue());
  }


  private static class Demo {

    private String demo;

    String getDemo() {
      return demo;
    }

    void setDemo(String value) {
      this.demo = value;
    }

    @Override
    public String toString() {
      return "Demo [value=" + this.demo + "]";
    }
  }

  public static class DemoRowMapper implements RowMapper<Demo> {

    @Override
    public Demo mapRow(ResultSet rs, int rowNum) throws SQLException {
      Demo demo = new Demo();
      demo.setDemo(rs.getString("CODE"));
      return demo;
    }
  }

  @Component
  public static class MyFlowAdapter extends IntegrationFlowAdapter {

    @Autowired
    @Qualifier("dataSource")
    DataSource dataSource;

    @Override
    protected IntegrationFlowDefinition<?> buildFlow() {

      JdbcPollingChannelAdapter adapter =
          new JdbcPollingChannelAdapter(dataSource, "SELECT CODE FROM DEMO");
      adapter.setRowMapper(new DemoRowMapper());
      adapter.setMaxRowsPerPoll(1);

      return from(adapter,
          c -> c.poller(Pollers.fixedRate(1000L, 2000L)
              .maxMessagesPerPoll(1)
              .get()))
          .channel(outputQueue);
    }
  }
}

EDIT I've simplified it as much as I can, refactoring to code below. The test passes a flow with a generic message source, and fails on a flow with JdbcPollingChannelAdapter message source. It's just not evident to me how I should configure the second message source so that it will suceed like the first message source.

  @Test
  @Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD,
      statements = "Create Table DEMO (CODE VARCHAR(5));")
  @Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD,
      statements = "Insert into DEMO (CODE) VALUES ('12345');")
  public void Should_HaveMessageOnTheQueue_When_UnsentDemosIsInTheDatabase() {

this.genericFlowContext.registration(new GenericFlowAdapter()).register();

PollableChannel genericChannel = this.beanFactory.getBean("GenericFlowAdapterOutput",
    PollableChannel.class);

this.jdbcPollingFlowContext.registration(new JdbcPollingFlowAdapter()).register();

PollableChannel jdbcPollingChannel = this.beanFactory.getBean("JdbcPollingFlowAdapterOutput",
    PollableChannel.class);

assertThat(genericChannel.receive(5000).getPayload(), equalTo("15317"));

assertThat(jdbcPollingChannel.receive(5000).getPayload(), equalTo("15317"));
  }

  private static class GenericFlowAdapter extends IntegrationFlowAdapter {

@Override
protected IntegrationFlowDefinition<?> buildFlow() {
  return from(getObjectMessageSource(),
      e -> e.poller(Pollers.fixedRate(100)))
      .channel(c -> c.queue("GenericFlowAdapterOutput"));
}

private MessageSource<Object> getObjectMessageSource() {
  return () -> new GenericMessage<>("15317");
}
}

private static class JdbcPollingFlowAdapter extends IntegrationFlowAdapter {

@Autowired
@Qualifier("dataSource")
DataSource dataSource;

@Override
protected IntegrationFlowDefinition<?> buildFlow() {
  return from(getObjectMessageSource(),
      e -> e.poller(Pollers.fixedRate(100)))
      .channel(c -> c.queue("JdbcPollingFlowAdapterOutput"));
}

private MessageSource<Object> getObjectMessageSource() {
  JdbcPollingChannelAdapter adapter =
      new JdbcPollingChannelAdapter(dataSource, "SELECT CODE FROM DEMO");
  adapter.setRowMapper(new DemoRowMapper());
  adapter.setMaxRowsPerPoll(1);
  return adapter;
}
  }

1 Answers1

1

Looks like you need to add @EnableIntegration to your test configuration. When you use Spring Boot slices for testing, not all auto-configurations are loaded:

https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#test-auto-configuration

UPDATE

The problem that JdbcPollingChannelAdapter is run in the separate, scheduled thread, already out of the original transaction around test method, where those @Sqls are performed.

The fix for you is like this:

@Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD,
      statements = "Insert into DEMO (CODE) VALUES ('12345');",
      config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))

Pay attention to that SqlConfig.TransactionMode.ISOLATED. This way the INSERT transaction is committed and the data is available for that separate polling thread for the JdbcPollingChannelAdapter.

Also pay attention that this JdbcPollingChannelAdapter always returns a List of records. So, your assertThat(jdbcPollingChannel.receive(5000).getPayload(), ...); should be against a List<String> even if there is only one record in the table.

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • Yes, you're right of course. Thank you. Unfortunately, however, adding the annotation turned out not to be the solution, polling the queue is still returning a null in my case – Curtis Olson Jan 19 '18 at 21:49
  • OK. Maybe `@ComponentScan` needs to be there as well? How does application context understand that your `MyFlowAdapter` must be scanned since we are in test slice anyway ? – Artem Bilan Jan 19 '18 at 21:52
  • I've pasted an edit above. At this point, the similarity of the two flows makes me want to believe that the test context is sufficient. As noted above, I've simplified it as much as I can. The test passes a flow with a generic message source, and fails on a flow with JdbcPollingChannelAdapter message source. It's just not evident to me how I should configure the second message source so that it will succeed like the first message source. – Curtis Olson Jan 20 '18 at 03:39
  • Strange. May I have a simple Spring Boot project to play from my side? Thanks – Artem Bilan Jan 20 '18 at 03:48
  • Of course. How would you prefer that I transfer it to you? – Curtis Olson Jan 20 '18 at 04:48
  • Via GitHub, please – Artem Bilan Jan 20 '18 at 13:37
  • Thank you, please find link below: https://github.com/gullywompr/jdbcpollingchanneladapter-dsl-experiment.git – Curtis Olson Jan 20 '18 at 16:18
  • That was great sample to follow. Thank you! Now, please, find an `UPDATE` in my answer for the solution. – Artem Bilan Jan 22 '18 at 15:37
  • IT WORKS! Artem, thanks for your kindness and patience. Let me know if I can return the favor. Cheers! – Curtis Olson Jan 23 '18 at 00:34
  • Cool! The favor you did is enough and that is confirmation that the feature works. Cheers! See you in other questions! Or even don't hesitate to contribute back to the project! – Artem Bilan Jan 23 '18 at 02:01