Issue
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
Solution
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();
}
}
Answered By - Anas Darrazi