Skip to content

Instantly share code, notes, and snippets.

@fmbenhassine
Last active October 17, 2021 12:42
Show Gist options
  • Save fmbenhassine/163a4103d8850cf95b575020c167f1cc to your computer and use it in GitHub Desktop.
Save fmbenhassine/163a4103d8850cf95b575020c167f1cc to your computer and use it in GitHub Desktop.
Spring Batch Bean Validation example #SpringBatch
package com.example.demovalidation;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoValidationApplication {
public static void main(String[] args) {
SpringApplication.run(DemoValidationApplication.class, args);
}
}
package com.example.demovalidation;
import java.util.Arrays;
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.support.ListItemReader;
import org.springframework.batch.item.validator.SpringValidator;
import org.springframework.batch.item.validator.ValidatingItemProcessor;
import org.springframework.batch.item.validator.Validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableBatchProcessing
public class JobConfiguration {
@Autowired
private JobBuilderFactory jobs;
@Autowired
private StepBuilderFactory steps;
@Bean
public ItemReader<Person> itemReader() {
Person person1 = new Person(1, "foo");
Person person2 = new Person(2, "");
return new ListItemReader<>(Arrays.asList(person1, person2));
}
@Bean
public ItemWriter<Person> itemWriter() {
return list -> {
for (Person person : list) {
System.out.println("person = " + person);
}
};
}
@Bean
public org.springframework.validation.Validator validator() {
// see https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring
return new org.springframework.validation.beanvalidation.LocalValidatorFactoryBean();
}
@Bean
public Validator<Person> springValidator() {
SpringValidator<Person> springValidator = new SpringValidator<>();
springValidator.setValidator(validator());
return springValidator;
}
@Bean
public ItemProcessor<Person, Person> itemProcessor() {
ValidatingItemProcessor<Person> validatingItemProcessor = new ValidatingItemProcessor<>(springValidator());
validatingItemProcessor.setFilter(true);
return validatingItemProcessor;
}
@Bean
public Step step1() {
return steps.get("step1")
.<Person, Person>chunk(1)
.reader(itemReader())
.processor(itemProcessor())
.writer(itemWriter())
.build();
}
@Bean
public Job job() {
return jobs.get("job")
.start(step1())
.build();
}
// can use a listener to log invalid items to file, db, etc
// use #afterProcess or #onProcessError depending on validatingItemProcessor.setFilter to true or false
static class InvalidItemsListener implements ItemProcessListener<Person, Person> {
@Override
public void beforeProcess(Person person) {
}
@Override
public void afterProcess(Person person, Person result) {
if (result == null) {
System.out.println(person + " has been filtered because it is invalid");
}
}
@Override
public void onProcessError(Person person, Exception e) {
System.out.println(person + " is invalid due to " + e.getMessage() );
}
}
}
package com.example.demovalidation;
import javax.validation.constraints.NotEmpty;
public class Person {
private int id;
@NotEmpty
private String name;
public Person() {
}
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo-validation</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo-validation</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</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-validation</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</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>
</plugin>
</plugins>
</build>
</project>
@netrapalsingh
Copy link

How can we log the validation failed records in log file, Thanks.

@fmbenhassine
Copy link
Author

fmbenhassine commented May 18, 2020

@netrapalsingh Looks like I missed the notification about your comment.. sorry for that.

Good question. There are two ways to do it depending on how you decide to deal with invalid items: either you filter them, or you throw an exception. This is configured with the filter flag: validatingItemProcessor.setFilter(true);. So:

  • If you decide to filter them, you can use ItemProcessListener#afterProcess method
  • If you decide to throw an exception, you can use ItemProcessListener#onError method

Here is a quick example:

class InvalidItemsListener implements ItemProcessListener<Person, Person> {

	@Override
	public void beforeProcess(Person person) {
	}

	@Override
	public void afterProcess(Person person, Person result) {
		if (result == null) {
			System.out.println(person + " has been filtered because it is invalid");
		}
	}

	@Override
	public void onProcessError(Person person, Exception e) {
		System.out.println(person + " is invalid due to " + e.getMessage() );
	}
} 

This example writes info to the standard output, but you can update it to write to a file. Of course, you need to register this listener in the step. Let me know if this helps.

@agru-capco
Copy link

Hi Benas,

I am reading the records from a csv file, I need to do validate the recodes and write errors to a file with line numbers, how can I get access to line number?

Thanks,
Agru

@fmbenhassine
Copy link
Author

@agru-capco There is an interface called ItemCountAware that sets the number on each item. It could work in your case since the item number would be the same as the line number. What you need to do is make Person implement ItemCountAware and log the line number in the file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment