Implement batch operation for REST resources with Spring Batch

Background

Spring Batch allows us to perform large volumes of records from several resources such as File, Relational Database, and, JSON file to name a few.

In this article, we will explore how to implement batch operation that reads from REST resources with Spring Batch through JsonItemReader. We will retrieve a list of users from JSON Placeholder and save them into a database.

Job Configuration

Next is to implement the job that will be responsible to read from REST resource and save them into a database. Job consists of Step and Step consists of ItemReader and ItemWriter. We will implement all of them in UserJobConfiguration.

@Configuration
class UserJobConfiguration {

    private final JobRepository jobRepository;
    private final PlatformTransactionManager transactionManager;
    private final MongoOperations mongo;

    UserJobConfiguration(JobRepository jobRepository, PlatformTransactionManager transactionManager, MongoOperations mongo) {
        this.jobRepository = jobRepository;
        this.transactionManager = transactionManager;
        this.mongo = mongo;
    }

    @Bean
    public Job userJob() throws MalformedURLException {
        return new JobBuilder("userJob", jobRepository).start(step()).build();
    }

    private Step step() throws MalformedURLException {
        return new StepBuilder("userStep", jobRepository)
                .<User, User>chunk(10, transactionManager)
                .reader(reader())
                .writer(writer())
                .build();
    }

    private JsonItemReader<User> reader() throws MalformedURLException {
        JacksonJsonObjectReader<User> jsonObjectReader = new JacksonJsonObjectReader<>(User.class);

        jsonObjectReader.setMapper(new ObjectMapper());

        return new JsonItemReaderBuilder<User>()
                .name("userReader")
                .jsonObjectReader(jsonObjectReader)
                .resource(new UrlResource("https://jsonplaceholder.typicode.com/users"))
                .build();
    }

    private MongoItemWriter<User> writer() {
        return new MongoItemWriterBuilder<User>()
                .template(mongo)
                .build();
    }

}

From the code above, we can see that a URL form of Resource is assigned to JsonItemReader. We will depend on JacksonJsonObjectRader to convert response from JSON Placeholder to User object.

@Configuration
class UserJobConfiguration {

    private JsonItemReader<User> reader() throws MalformedURLException {
        JacksonJsonObjectReader<User> jsonObjectReader = new JacksonJsonObjectReader<>(User.class);

        jsonObjectReader.setMapper(new ObjectMapper());

        return new JsonItemReaderBuilder<User>()
                .name("userReader")
                .jsonObjectReader(jsonObjectReader)
                .resource(new UrlResource("https://jsonplaceholder.typicode.com/users"))
                .build();
    }

}

Now that we have implemented the Job, we can verify that it is working by executing an integration test.

Verification

We will launch userJob which will retrieve list of User from JSON Placeholder and save them into a database. Once completed then we will verify that the database contains the expected number of users.

@Testcontainers
@SpringBatchTest
@SpringJUnitConfig({ BatchTestConfiguration.class, MongoTestConfiguration.class, UserJobConfiguration.class })
class UserBatchJobTests {

    @Autowired
    private JobLauncherTestUtils launcher;

    @Autowired
    private MongoOperations mongoOperations;

    @Test
    @DisplayName("Given there are 10 users returned from REST Service When the job is COMPLETED Then all users should be saved to MongoDB")
    void launch() {

        await().atMost(ofSeconds(30)).untilAsserted(() -> {
            var execution = launcher.launchJob();

            assertThat(execution.getExitStatus()).isEqualTo(COMPLETED);
        });

        var persistedUsers = mongoOperations.findAll(User.class);

        assertThat(persistedUsers).hasSize(10);
    }

}

Full implementation can be found in UserBatchJobTests.