0

I am trying to load data from SQL server, apply some transformations and put it into CSV using the spring batch scheduler. All works fine when everything is in the same class.

This is my code:

package com.abc.tools.bootbatch;

@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
@Autowired
public JobBuilderFactory jobBuilderFactory;

@Autowired
public StepBuilderFactory stepBuilderFactory;

@Autowired
public DataSource dataSource;

private static final String qry = "select top 20 colA, colB, colC from ABC";
private Resource outputResource = new FileSystemResource("output/outputData.csv");

@Bean
public DataSource dataSource() {
    final DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(driver_class);
    dataSource.setUrl("db_url");
    dataSource.setUsername(usr);
    dataSource.setPassword(pwd);

    return dataSource;
}

@Bean
ItemReader<Trade> reader() {
    JdbcCursorItemReader<Trade> databaseReader = new JdbcCursorItemReader<>();

    databaseReader.setDataSource(dataSource);
    databaseReader.setSql(qry);
    databaseReader.setRowMapper(new BeanPropertyRowMapper<>(Trade.class));

    return databaseReader;
}

@Bean
public TradeProcessor processor() {
    return new TradeProcessor();
}

@Bean
public FlatFileItemWriter<Trade> writer()
{
    //Create writer instance
    FlatFileItemWriter<Trade> writer = new FlatFileItemWriter<>();

    //Set output file location
    writer.setResource(outputResource);

    //All job repetitions should "append" to same output file
    writer.setAppendAllowed(true);

    //Name field values sequence based on object properties
    writer.setLineAggregator(new DelimitedLineAggregator<Trade>() {
        {
            setDelimiter(",");
            setFieldExtractor(new BeanWrapperFieldExtractor<Trade>() {
                {
                    setNames(new String[] { "colA", "colB", "colC" });
                }
            });
        }
    });
    return writer;
}

@Bean
public Step step1() {
    return stepBuilderFactory.get("step1").<Trade, Trade> chunk(10)
            .reader(reader())
            .processor(processor())
            .writer(writer())
            .build();
}

@Bean
public Job exportUserJob() {
    return jobBuilderFactory.get("exportUserJob")
            .incrementer(new RunIdIncrementer())
            .flow(step1())
            .end()
            .build();
}

}

When I seperate the processing, loading and data reading in different classes, it works fine using autowire, unless I use batch job. On using the batch job it gives error in instantiating the database. So I removed the autowire and tried to do something like this:

@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
@Autowired
public JobBuilderFactory jobBuilderFactory;

@Autowired
public StepBuilderFactory stepBuilderFactory;

@Autowired
public DBConfig dbConfig;

public DataConnection dataconnection=new DataConnection();
DataReader reader=new DataReader();
TradeProcessor processor=new TradeProcessor();
FlatFileWriter flatFileWriter=new FlatFileWriter();

DataSource ds=dataconnection.getDataSource(dbConfig);


@Bean
public Step step1() {
    return stepBuilderFactory.get("step1").<Trade, Trade> chunk(10)
            .reader(reader.reader(ds))
            .processor(processor.processor())
            .writer(flatFileWriter.writer())
            .build();
}

@Bean
public Job exportUserJob() {
    return jobBuilderFactory.get("exportUserJob")
            .incrementer(new RunIdIncrementer())
            .flow(step1())
            .end()
            .build();
}

}

This gives Failed to initialize BatchConfiguration

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'batchConfiguration'

I think I am missing something to aggregate it all. I am new to Spring, any help is appreciated

Rashed Hasan
  • 3,721
  • 11
  • 40
  • 82
MagicBeans
  • 343
  • 1
  • 5
  • 18
  • This looks like a configuration / component scan problem but it's hard to say without also seeing how you've set your application up (component scan and configuration registry). See https://stackoverflow.com/questions/42111094/componentscan-with-multiple-configuration-class-annotation-based-configuratio – geco17 Jan 25 '20 at 09:53

1 Answers1

0

In your first example, you are autowiring a datasource and declaring a datasource bean in the same class which is incorrect. In the second example, instead of autowiring DBConfig, you can import it with @Import(DBConfig.class) and autowire the datasource in your job configuration as needed. Here is a typical configuration:

@Configuration
public class DBConfig {

   @Bean
   public DataSource dataSource() {
      final DriverManagerDataSource dataSource = new DriverManagerDataSource();
      dataSource.setDriverClassName(driver_class);
      dataSource.setUrl("db_url");
      dataSource.setUsername(usr);
      dataSource.setPassword(pwd);
      return dataSource;
    }

}

@Configuration
@EnableBatchProcessing
@Import(DBConfig.class)
public class BatchConfiguration {

   @Bean
   ItemReader<Trade> reader(DataSource datasource) {
      // use datasource to configure the reader
   }

}

Since you use Spring Boot, you can remove the DBConfig class, configure the datasource as needed in your application.properties file and the datasource will be automatically injected in your BatchConfiguration.

Mahmoud Ben Hassine
  • 28,519
  • 3
  • 32
  • 50