Spring Batch 2.2 - JavaConfig Part 3: Profiles and environments - codecentric AG Blog

:

This is the third post about the new Java based configuration features in Spring Batch 2.2. In the first post I compared the two configuration styles on a non-trivial batch job reading from a file and writing to a database. I used a very simple infrastructure setup with an In-Memory-Database and a DataSourceTransactionManager, and I didn’t have any property files to read configurable data from. This post is about adding another infrastructure configuration for a production environment.
In future posts I will talk about job inheritance, modular configurations and partitioning and multi-threaded step, everything regarding Java based configuration, of course. You can find the JavaConfig code examples on Github.

In production our batch jobs run on an application server, in this example the Websphere Application Server. The DataSource is configured in the application server and can be accessed via JNDI. We want to use the application server’s transaction features to be able to have XA transactions, so a DataSourceTransactionManager won’t be sufficient this time. The JNDI name of the DataSource shall be read from a properties file to make it configurable (you may argue that this doesn’t make too much sense, and I agree, but I wanna show how reading properties works).
Here’s the configuration:

@Configuration
@EnableBatchProcessing
@PropertySource("classpath:batch.properties")
public class WebsphereInfrastructureConfiguration implements BatchConfigurer, InfrastructureConfiguration {
 
	@Autowired
	private Environment env;
 
	@Bean
	public DataSource dataSource(){
		try {
			InitialContext initialContext = new InitialContext();
			return (DataSource) initialContext.lookup(env.getProperty("datasource.jndi"));
		} catch (NamingException e) {
			throw new RuntimeException("JNDI lookup failed.",e);
		}
	}
 
	public JobRepository getJobRepository() throws Exception {
		JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
		factory.setDataSource(dataSource());
		factory.setTransactionManager(getTransactionManager());
		factory.afterPropertiesSet();
		return  (JobRepository) factory.getObject();
	}
 
	public PlatformTransactionManager getTransactionManager() throws Exception {
		return new WebSphereUowTransactionManager();
	}
 
	public JobLauncher getJobLauncher() throws Exception {
		SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
		jobLauncher.setJobRepository(getJobRepository());
		jobLauncher.afterPropertiesSet();
		return jobLauncher;
	}
 
}

@Configuration @EnableBatchProcessing @PropertySource("classpath:batch.properties") public class WebsphereInfrastructureConfiguration implements BatchConfigurer, InfrastructureConfiguration { @Autowired private Environment env; @Bean public DataSource dataSource(){ try { InitialContext initialContext = new InitialContext(); return (DataSource) initialContext.lookup(env.getProperty("datasource.jndi")); } catch (NamingException e) { throw new RuntimeException("JNDI lookup failed.",e); } }public JobRepository getJobRepository() throws Exception { JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); factory.setDataSource(dataSource()); factory.setTransactionManager(getTransactionManager()); factory.afterPropertiesSet(); return (JobRepository) factory.getObject(); }public PlatformTransactionManager getTransactionManager() throws Exception { return new WebSphereUowTransactionManager(); }public JobLauncher getJobLauncher() throws Exception { SimpleJobLauncher jobLauncher = new SimpleJobLauncher(); jobLauncher.setJobRepository(getJobRepository()); jobLauncher.afterPropertiesSet(); return jobLauncher; }}

I’ll walk through the details now.

Implementing BatchConfigurer

The BatchConfigurer interface allows for creating our own JobRepository, JobLauncher and PlatformTransactionManager. If you’re not adding an implementation of the interface to your ApplicationContext (and you do that by implementing it in the configuration class since the configuration itself is a Spring bean), the annotation @EnableBatchProcessing creates a DefaultBatchConfigurer component. This component expects exactly one DataSource in the ApplicationContext and creates a DataSourceTransactionManager. If you’re running your jobs on an application server, the DefaultBatchConfigurer won’t be sufficient, because you need to use the application server’s transaction manager. That’s done by using the WebSphereUowTransactionManager in the example above. It’s also very common to have more than one DataSource in an application server environment, which would be another problem with the default behaviour.
The DataSource is looked up via JNDI this time instead of creating it directly.

Environment and @PropertySource

With the annotation @PropertySource you may specify files as sources for properties, in this case we look for a file named batch.properties on the classpath and add all properties in it to Spring’s Environment. The Environment can be injected into configuration classes to use those properties. In the example above we take the property datasource.jndi and use it as the DataSource‘s JNDI name. Properties may come from many different kinds of PropertySources, for example there are automatically PropertySources for environment variables and JVM properties registered when you start an ApplicationContext.

Implementing InfrastructureConfiguration

We have the interface InfrastructureConfiguration, and of course we’re gonna implement it this time as well. As you might remember we need an InfrastructureConfiguration in our job configuration, but the job configuration doesn’t care about the implementation – perfect exchangeability! We can use the job configuration with all kinds of infrastructure configurations as long as the contract InfrastructureConfiguration is met.

We saw what to do if the default behaviour of the annotation @EnableBatchProcessing is not enough: add an implementation of BatchConfigurer to your ApplicationContext. We saw how to use properties in Java based configuration.
And again, we saw one advantage of Java based configuration: the possibility to define a contract for a configuration with an interface, in this case the InfrastructureConfiguration. Configuration code that uses that configuration doesn’t have to care about the implementation, and we may add new implementations like the one in this post without affecting the other configuration classes.