I’m working on a simple Spring Batch application, and when I finished configuring it, I found that problem:
The dependencies of some of the beans in the application context form a cycle:
jobRestController defined in file [/home/yassine/Downloads/demo/target/classes/com/example/demo/JobRestController.class] springBatchConfig defined in file [/home/yassine/Downloads/demo/target/classes/com/example/demo/SpringBatchConfig.class]
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
The implementation of the two classes:
SpringBatchConfig:
import lombok.AllArgsConstructor; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemWriter; import org.springframework.batch.item.file.FlatFileItemReader; import org.springframework.batch.item.file.LineMapper; import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper; import org.springframework.batch.item.file.mapping.DefaultLineMapper; import org.springframework.batch.item.file.transform.DelimitedLineTokenizer; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; @Configuration @EnableBatchProcessing @AllArgsConstructor public class SpringBatchConfig { private JobBuilderFactory jobBuilderFactory; private StepBuilderFactory stepBuilderFactory; private ItemReader<BankTransaction> bankTransactionItemReader; private ItemProcessor<BankTransaction, BankTransaction> bankTransactionItemProcessor; private ItemWriter<BankTransaction> bankTransactionItemWriter; @Bean public Job bankJob() { Step step1 = stepBuilderFactory.get("step-load-data") .<BankTransaction, BankTransaction>chunk(100) .reader(bankTransactionItemReader) .processor(bankTransactionItemProcessor) .writer(bankTransactionItemWriter) .build(); return jobBuilderFactory.get("bank-data-loader-job") .start(step1) .build(); } @Bean public FlatFileItemReader<BankTransaction> flatFileItemReader(@Value("${inputFile}") Resource inputFile) { FlatFileItemReader<BankTransaction> flatFileItemReader = new FlatFileItemReader<>(); flatFileItemReader.setName("CSV-READER"); flatFileItemReader.setLinesToSkip(1); flatFileItemReader.setResource(inputFile); flatFileItemReader.setLineMapper(lineMapper()); return flatFileItemReader; } @Bean public LineMapper<BankTransaction> lineMapper() { DefaultLineMapper<BankTransaction> lineMapper = new DefaultLineMapper<>(); DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer(); lineTokenizer.setDelimiter(","); lineTokenizer.setStrict(false); lineTokenizer.setNames("id", "accountID", "strTransactionDate", "transactionType", "amount"); lineMapper.setLineTokenizer(lineTokenizer); BeanWrapperFieldSetMapper<BankTransaction> fieldSetMapper = new BeanWrapperFieldSetMapper<>(); fieldSetMapper.setTargetType(BankTransaction.class); lineMapper.setFieldSetMapper(fieldSetMapper); return lineMapper; } }
JobRestController:
import lombok.AllArgsConstructor; import org.springframework.batch.core.*; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController @AllArgsConstructor public class JobRestController { private JobLauncher jobLauncher; private Job job; @GetMapping("/startJob") public BatchStatus load() throws Exception { Map<String, JobParameter> parameters = new HashMap<>(); parameters.put("time", new JobParameter(System.currentTimeMillis())); JobParameters jobParameters = new JobParameters(parameters); JobExecution jobExecution = jobLauncher.run(job, jobParameters); while (jobExecution.isRunning()) { System.out.println("....."); } return jobExecution.getStatus(); } }
pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>demo</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-batch</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.batch</groupId> <artifactId>spring-batch-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
I couldn’t fix it, and I didn’t find any similar problem on StackOverflow. Thank you in advance
Advertisement
Answer
today i had the same problem and i solve it with these steps:
- you should create ItemWriter,ItemProcessor Beans
- declare the Job bean in another config class
spring batch config
package com.example.demo.config; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.List; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemWriter; import org.springframework.batch.item.file.FlatFileItemReader; import org.springframework.batch.item.file.LineMapper; import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper; import org.springframework.batch.item.file.mapping.DefaultLineMapper; import org.springframework.batch.item.file.transform.DelimitedLineTokenizer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import com.example.demo.dao.Person; import com.example.demo.dao.PersonRepository; import lombok.RequiredArgsConstructor; @Configuration @EnableBatchProcessing @RequiredArgsConstructor public class SpringBatchConfig { @Bean public FlatFileItemReader<Person> getItemReader(@Value("${filePath}") Resource resource){ FlatFileItemReader<Person> itemReader = new FlatFileItemReader<Person>(); itemReader.setName("CSV-READER"); itemReader.setLinesToSkip(1); itemReader.setResource(resource); itemReader.setLineMapper(PersonLineMapper()); return itemReader; } @Bean public LineMapper<Person> PersonLineMapper(){ DefaultLineMapper<Person> lineMapper = new DefaultLineMapper<Person>(); DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer(); lineTokenizer.setStrict(false); lineTokenizer.setDelimiter(";"); lineTokenizer.setNames("id","fName","lName","strBirthDate","gender","dispo"); lineMapper.setLineTokenizer(lineTokenizer); BeanWrapperFieldSetMapper<Person> fieldSetMapper = new BeanWrapperFieldSetMapper<Person>(); fieldSetMapper.setTargetType(Person.class); lineMapper.setFieldSetMapper(fieldSetMapper); return lineMapper; } @Bean public ItemProcessor<Person, Person> getItemProcessor(){ return new ItemProcessor<Person, Person>() { private DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); @Override public Person process(Person item) throws Exception { item.setBirthDate(dateFormat.parse(item.getStrBirthDate())); return item; } }; } @Bean public ItemWriter<Person> getItemWriter() { return new ItemWriter<Person>() { @Autowired private PersonRepository personRepository; @Override public void write(List<? extends Person> items) throws Exception { personRepository.saveAll(items); } }; } }
Job config
package com.example.demo.config; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemWriter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.example.demo.dao.Person; import lombok.RequiredArgsConstructor; @Configuration @RequiredArgsConstructor public class JobConfig { private final JobBuilderFactory jobBuilderFactory; private final StepBuilderFactory stepBuilderFactory; private final ItemReader<Person> personItemReader; private final ItemWriter<Person> peronItemWriter; private final ItemProcessor<Person, Person> personItemProcessor; @Bean public Job personJob() { Step step1 = this.stepBuilderFactory.get("step-load-data") .<Person,Person>chunk(100) .writer(peronItemWriter) .reader(personItemReader) .processor(personItemProcessor) .build(); return this.jobBuilderFactory.get("person-data-loader-job") .start(step1) .build(); } }
rest controller for launch the job
package com.example.demo.web; import java.util.HashMap; import java.util.Map; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParameter; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import lombok.RequiredArgsConstructor; @RestController @RequiredArgsConstructor public class PersonRestController { private final JobLauncher jobLauncher; private final Job job; @GetMapping("/startBatch") public BatchStatus load() throws Exception{ Map<String, JobParameter> parameters = new HashMap<>(); parameters.put("time", new JobParameter(System.currentTimeMillis())); JobParameters jobParameters = new JobParameters(parameters); JobExecution jobExecution = jobLauncher.run(job, jobParameters); while(jobExecution.isRunning()) { System.out.println("...."); } return jobExecution.getStatus(); } }