Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Batch - Modular Batch Processing scoping rules

I've got a toy project that imports a list of names as people and monsters to the database, then using then changes the last name of the monsters to Monster. To write items to the database I'm using the HibernateItemWriter

Each of the 3 jobs has its own configuration file and they are bundled in a single configuration with @EnableBatchProcessing(modular=true) annotation.

This all works.

However when the the @EnableBatchProcessing is present on the configuration files of the individual jobs the HibernateItemWriter can no longer find a hibernate session.

Can someone explain the application context scoping rules that make this happen?

It is rather nice to have the @EnableBatchProcessing on individual Job configurations when not using them individually. It allows them to be imported and ran while spring auto configuration does the work.

The 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());

    }

}

The 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);
    }
}

The Hibernate Config

@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;
    }

}

A job configuration file. Adding @EnableBatchProcessing here will cause the HibernateItemWriter be unable to find a 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();
    }

}

The exception thrown.

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)
like image 300
M.P. Korstanje Avatar asked May 17 '26 07:05

M.P. Korstanje


1 Answers

You're providing your own transaction manager. To override the ones that Spring Batch provides with @EnableBatchProcessing, you need to provide your own BatchConfigurer implementation. Otherwise, we're going to add one for you and in this case, it's not going to be the Hibernate based transaction manager. Have HibernateConfiguration extend DefaultBatchConfigurer and override the getTransactionManager method with your transaction manager code.

like image 123
Michael Minella Avatar answered May 19 '26 21:05

Michael Minella



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!