This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Batch Documentation 6.0.5!

Reusing Existing Services and APIs

Batch systems are often used in conjunction with other application styles. The most common is an online system, but it may also support integration or even a thick client application by moving necessary bulk data that each application style uses. For this reason, it is common that many users want to reuse existing DAOs, services, or other APIs within their batch jobs. The Spring container itself makes this fairly easy by allowing any necessary class to be injected. However, there may be cases where the existing service or API needs to act as an ItemReader or ItemWriter, either to satisfy the dependency of another Spring Batch class or because it truly is the main ItemReader for a step.

Reusing Existing Services

It is fairly trivial to write an adapter class for each service that needs wrapping, but because it is such a common concern, Spring Batch provides implementations: ItemReaderAdapter and ItemWriterAdapter. Both classes implement the standard Spring method by invoking the delegate pattern and are fairly simple to set up.

  • Java

  • XML

The following Java example uses the ItemReaderAdapter:

Java Configuration
@Bean
public ItemReaderAdapter itemReader() {
	ItemReaderAdapter reader = new ItemReaderAdapter();

	reader.setTargetObject(fooService());
	reader.setTargetMethod("generateFoo");

	return reader;
}

@Bean
public FooService fooService() {
	return new FooService();
}

The following XML example uses the ItemReaderAdapter:

XML Configuration
<bean id="itemReader" class="org.springframework.batch.infrastructure.item.adapter.ItemReaderAdapter">
    <property name="targetObject" ref="fooService" />
    <property name="targetMethod" value="generateFoo" />
</bean>

<bean id="fooService" class="org.springframework.batch.infrastructure.item.sample.FooService" />

One important point to note is that the contract of the targetMethod must be the same as the contract for read: When exhausted, it returns null. Otherwise, it returns an Object. Anything else prevents the framework from knowing when processing should end, either causing an infinite loop or incorrect failure, depending upon the implementation of the ItemWriter.

  • Java

  • XML

The following Java example uses the ItemWriterAdapter:

Java Configuration
@Bean
public ItemWriterAdapter itemWriter() {
	ItemWriterAdapter writer = new ItemWriterAdapter();

	writer.setTargetObject(fooService());
	writer.setTargetMethod("processFoo");

	return writer;
}

@Bean
public FooService fooService() {
	return new FooService();
}

The following XML example uses the ItemWriterAdapter:

XML Configuration
<bean id="itemWriter" class="org.springframework.batch.infrastructure.item.adapter.ItemWriterAdapter">
    <property name="targetObject" ref="fooService" />
    <property name="targetMethod" value="processFoo" />
</bean>

<bean id="fooService" class="org.springframework.batch.infrastructure.item.sample.FooService" />

Reusing Existing APIs

Besides services, it is also common to want to reuse an existing API that exposes its data through a Java Iterator (or Iterable), without having to write a dedicated ItemReader for it. Spring Batch provides IteratorItemReader for exactly that purpose: it adapts any Iterator or Iterable to the ItemReader contract.

A good example is the scrolling support introduced in Spring Data Commons 3.1, which exposes a WindowIterator to iterate over successive Window instances returned by a repository, using either offset-based or keyset-based scrolling.

  • Java

  • XML

The following Java example shows how to reuse a WindowIterator obtained from a Spring Data repository as an ItemReader, without needing a dedicated reader implementation:

Java Configuration
interface UserRepository extends Repository<User, Long> {

	Window<User> findFirst10ByLastnameOrderByFirstname(String lastname, KeysetScrollPosition position);

}

@Bean
public ItemReader<User> itemReader(UserRepository userRepository) {
	WindowIterator<User> users = WindowIterator
		.of(position -> userRepository.findFirst10ByLastnameOrderByFirstname("Doe", position))
		.startingAt(ScrollPosition.keyset());

	return new IteratorItemReader<>(users);
}

Building a WindowIterator requires a lambda expression, so it cannot be expressed directly as a <bean> definition. The construction logic can instead be extracted to a factory method and referenced from XML, as follows:

Factory class
public class UserItemReaderFactory {

	public static ItemReader<User> createItemReader(UserRepository userRepository) {
		WindowIterator<User> users = WindowIterator
			.of(position -> userRepository.findFirst10ByLastnameOrderByFirstname("Doe", position))
			.startingAt(ScrollPosition.keyset());

		return new IteratorItemReader<>(users);
	}

}
XML Configuration
<bean id="itemReader" class="org.springframework.batch.infrastructure.item.sample.UserItemReaderFactory"
      factory-method="createItemReader">
    <constructor-arg ref="userRepository" />
</bean>

This pattern is not specific to Spring Data’s scrolling API: any API that exposes an Iterator or an Iterable can be adapted to an ItemReader in the same way.