Spring Batching Batch Modulation Rules
I have a toy project that imports a list of names as people and monsters into a database, and then uses the then Monster name changes. To write items to the database I am using HibernateItemWriter
Each of the three jobs has its own configuration file, and they are combined into one configuration with the @EnableBatchProcessing annotation (modular = true).
It all works.
However, when @EnableBatchProcessing is present in the individual job configuration files, the HibernateItemWriter can no longer find the hibernate session.
Can someone explain the rules for determining the application context to make this happen?
It's pretty nice to have @EnableBatchProcessing in individual Job configurations when you're not using them separately. It allows them to be imported and run while spring does auto-configuration.
application
@Import({ImportAllConfiguration.class, EmbededDatasourceConfiguration.class, HibernateConfiguration.class})
@Configuration
public class Application {
public static void main(String[] args) throws SQLException, BeansException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, NoSuchJobException, JobParametersInvalidException {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
JobRegistry jobRegistry = ctx.getBean(JobRegistry.class);
Job job1 = jobRegistry.getJob("importMonsterJob");
JobLauncher launcher = ctx.getBean(JobLauncher.class);
launcher.run(job1, new JobParameters());
}
}
ImportAllConfiguration
@Configuration
@EnableBatchProcessing(modular=true)
public class ImportAllConfiguration {
@Bean
public ApplicationContextFactory someJobs() {
return new GenericApplicationContextFactory(ImportMonsterConfiguration.class);
}
@Bean
public ApplicationContextFactory someMoreJobs() {
return new GenericApplicationContextFactory(ImportPersonConfiguration.class);
}
@Bean
public ApplicationContextFactory evenMoreJobs() {
return new GenericApplicationContextFactory(MatchMonsterToPersonConfiguration.class);
}
}
Hibernate configuration
@Configuration
public class HibernateConfiguration {
@Autowired
private DataSource dataSource;
@Bean
public HibernateTransactionManager transactionManager(
SessionFactory sessionFactory) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory);
return txManager;
}
@Bean
public JdbcTemplate jdbcTemplate() {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@SuppressWarnings("serial")
public Properties hibernateProperties() {
return new Properties() {
{
//setProperty(Environment.CURRENT_SESSION_CONTEXT_CLASS, "thread");
setProperty(Environment.HBM2DDL_AUTO, "create");
setProperty(Environment.SHOW_SQL, "true");
}
};
}
@Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean annotationSessionFactoryBean = new LocalSessionFactoryBean();
annotationSessionFactoryBean.setPackagesToScan("hello.model");
annotationSessionFactoryBean.setDataSource(dataSource);
annotationSessionFactoryBean
.setHibernateProperties(hibernateProperties());
return annotationSessionFactoryBean;
}
}
Job configuration file. Adding @EnableBatchProcessing here will result in the HibernateItemWriter not being able to find the session.
@Configuration
@EnableBatchProcessing <-- This causes the exception
public class ImportMonsterConfiguration {
@Autowired
protected SessionFactory sessionFactory;
@Autowired
protected StepBuilderFactory stepBuilderFactory;
@Autowired
protected JobBuilderFactory jobBuilderFactory;
@Bean
public <T> ItemWriter<T> writer() {
return new HibernateItemWriter<T>() {
{
setSessionFactory(sessionFactory);
}
};
}
@Bean
public ItemReader<Monster> reader() {
FlatFileItemReader<Monster> reader = new FlatFileItemReader<Monster>();
reader.setResource(new ClassPathResource("sample-data.csv"));
reader.setLineMapper(new DefaultLineMapper<Monster>() {
{
setLineTokenizer(new DelimitedLineTokenizer() {
{
setNames(new String[] { "firstName", "lastName" });
}
});
setFieldSetMapper(new BeanWrapperFieldSetMapper<Monster>() {
{
setTargetType(Monster.class);
}
});
}
});
return reader;
}
@Bean
public ItemProcessor<Monster, Monster> processor() {
return new MonsterItemProcessor();
}
@Bean
public Job importMonsterJob() {
return jobBuilderFactory.get("importMonsterJob")
.incrementer(new RunIdIncrementer()).flow(step2()).end()
.build();
}
@Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
return validator;
}
@Bean
public ItemProcessor<Monster, Monster> validatingProcessor() {
SpringValidator<Monster> springValidator = new SpringValidator<Monster>();
springValidator.setValidator(validator());
ValidatingItemProcessor<Monster> validatingItemProcessor = new ValidatingItemProcessor<Monster>();
validatingItemProcessor.setValidator(springValidator);
return validatingItemProcessor;
}
@Bean
public Step step2() {
return stepBuilderFactory.get("step2").<Monster, Monster> chunk(10)
.reader(reader()).processor(processor())
.processor(validatingProcessor()).writer(writer()).build();
}
}
An exception.
org.hibernate.HibernateException: No Session found for current thread
at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:106)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014)
at org.springframework.batch.item.database.HibernateItemWriter.doWrite(HibernateItemWriter.java:134)
at org.springframework.batch.item.database.HibernateItemWriter.write(HibernateItemWriter.java:113)
source to share
You provide your own transaction manager. To override the ones that Spring Batch provides with @EnableBatchProcessing
you need to provide your own implementation BatchConfigurer
. Otherwise, we'll add it for you, in which case it won't be a Hibernate based transaction manager. HibernateConfiguration
extend DefaultBatchConfigurer
and override the method getTransactionManager
with your transaction manager code.
source to share