Home > Software design >  Spring Batch job not being called in Spring Boot
Spring Batch job not being called in Spring Boot

Time:12-19

I am trying to run a Spring Batch job that takes in a CSV file and writes it into a HSQL imbedded database. My program is supposed to log when each row of the CSV is added to the DB, but it is not.I am getting no errors when running, but I know the job is not being called since it is not logging anything into my console.

This is my file system

This is my BatchConfig.java (defines the Batch Job)

package io.davidwilliford.wcdashboard.data;

import javax.sql.DataSource;

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.job.builder.JobBuilder;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.transaction.PlatformTransactionManager;

import io.davidwilliford.wcdashboard.model.Match;

/**
 * Configuration class for our Batch I/O
 * 
 * Reads, Processes and Writes from CSV file
 * Called for every line in our CSV
 */

@Configuration
@EnableBatchProcessing
@ComponentScan("io.davidwilliford")
public class BatchConfig {

    // includes EVERY field of CSV file
    private final String[] FIELD_NAMES = new String[] {
            "match", "dayofweek", "match_time", "home_team", "away_team", "home_xg", "away_xg", "score", "attendance",
            "venue", "referee", "home_formation", "away_formation", "home_captain", "away_captain", "home_manager",
            "away_manager", "home_possession", "away_possession", "home_completed_passes", "home_attempted_pases",
            "away_completed_passes", "away_attempted_pases", "home_sot", "away_sot", "home_total_shots",
            "away_total_shots", "home_saves", "away_saves", "home_fouls", "away_fouls", "home_corners", "away_corners",
            "home_crosses", "away_crosses", "home_touches", "away_touches", "home_tackles", "away_tackles",
            "home_interceptions", "away_interceptions", "home_aerials_won", "away_aerials_won", "home_clearances",
            "away_clearances", "home_offsides", "away_offsides", "home_gks", "away_gks", "home_throw_ins",
            "away_throw_ins", "home_long_balls", "away_long_balls"
    };

    /**
     * Reader
     * 
     * Reads from CSV file
     * And turns into MatchInput object
     */
    @Bean
    public FlatFileItemReader<MatchInput> reader() {
        return new FlatFileItemReaderBuilder<MatchInput>()
                .name("MatchItemReader")
                .resource(new ClassPathResource("match-data.csv"))
                .linesToSkip(1)
                .delimited()
                .names(FIELD_NAMES)
                .fieldSetMapper(new BeanWrapperFieldSetMapper<MatchInput>() {
                    {
                        setTargetType(MatchInput.class);
                    }
                })
                .build();
    }

    /**
     * Processor
     * 
     * Takes in MatchInput objects and makes Match objects
     * Is encapsulated into its own class
     */
    @Bean
    public MatchDataProcessor processor() {
        return new MatchDataProcessor();
    }

    /**
     * Writer
     * 
     * Writes the information into Match objects
     */
    @Bean
    public JdbcBatchItemWriter<Match> writer(DataSource dataSource) {
        return new JdbcBatchItemWriterBuilder<Match>()
                .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>())
                .sql("INSERT INTO matches (id, dayofweek, match_time, home_team, away_team, home_score, away_score, attendance, venue, referee, home_captain, away_captain,  home_manager, away_manager, home_total_shots, away_total_shots) "
                         
                        "VALUES (:id, :dayofweek, :match_time, :home_team, :away_team, :home_score, :away_score, :attendance, :venue, :referee, :home_captain, :away_captain, : home_manager, :away_manager, :home_total_shots, :away_total_shots)")
                .dataSource(dataSource)
                .build();
    }

    /**
     * Create a Job whose steps are to Read, Process and Write
     */
    @Bean
    public Job importUserJob(JobRepository jobRepository, JobCompletionNotificationListener listener, Step step1) {
        return new JobBuilder("importUserJob", jobRepository)
                .incrementer(new RunIdIncrementer())
                .listener(listener)
                .flow(step1)
                .end()
                .build();
    }

    /**
     * This is the step for the above job
     * Calls reader, processor and writer (written above)
     */
    @Bean
    public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager,
            JdbcBatchItemWriter<Match> writer) {
        return new StepBuilder("step1", jobRepository)
                .<MatchInput, Match>chunk(10, transactionManager)
                .reader(reader())
                .processor(processor())
                .writer(writer)
                .build();
    }
}

This is my MatchInput.java class

package io.davidwilliford.wcdashboard.data;

/**
 * Class for match input
 * 
 * Match will work as our Id
 * In total, there are 63 matches
 */

public class MatchInput {

    /**
     * Class attributes
     */
    private String match;
    private String dayofweek;
    private String match_time;
    private String home_team;
    private String away_team;
    private String home_xg;
    private String away_xg;
    private String score;
    private String attendance;
    private String venue;
    private String referee;
    private String home_formation;
    private String away_formation;
    private String home_captain;
    private String away_captain;
    private String home_manager;
    private String away_manager;
    private String home_possession;
    private String away_possession;
    private String home_completed_passes;
    private String home_attempted_pases;
    private String away_completed_passes;
    private String away_attempted_pases;
    private String home_sot;
    private String away_sot;
    private String home_total_shots;
    private String away_total_shots;
    private String home_saves;
    private String away_saves;
    private String home_fouls;
    private String away_fouls;
    private String home_corners;
    private String away_corners;
    private String home_crosses;
    private String away_crosses;
    private String home_touches;
    private String away_touches;
    private String home_tackles;
    private String away_tackles;
    private String home_interceptions;
    private String away_interceptions;
    private String home_aerials_won;
    private String away_aerials_won;
    private String home_clearances;
    private String away_clearances;
    private String home_offsides;
    private String away_offsides;
    private String home_gks;
    private String away_gks;
    private String home_throw_ins;
    private String away_throw_ins;
    private String home_long_balls;
    private String away_long_balls;
   
    // purposely left out getters and setters since they arent important 

}

This is my Match.java model class

package io.davidwilliford.wcdashboard.model;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

/**
 * This is the model class for all of our matches
 * 
 * @Entity creates a table with this class
 */
@Entity
@Table(name = "matches")
public class Match {

    /**
     * Class attributes
     */

    @Id
    private int id;
    private String dayofweek;
    private String match_time;
    private String home_team;
    private String away_team;
    private String home_score;
    private String away_score;
    private String attendance;
    private String venue;
    private String referee;
    private String home_captain;
    private String away_captain;
    private String home_manager;
    private String away_manager;
    private String home_total_shots;
    private String away_total_shots;

    /**
     * toString method
     */

    @Override
    public String toString() {
        return "Home Team: "   this.home_team   ", Away Team: "   this.away_team;
    }

    /**
     * Getters and Setters
     */

    public long getId() {

}

And this is my JobCompleteNotificationListener.java class

This class is supposed to log the names from the home and away team upon completetion.

package io.davidwilliford.wcdashboard.data;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class JobCompletionNotificationListener implements JobExecutionListener {

    private static final Logger log = LoggerFactory.getLogger(JobCompletionNotificationListener.class);

    private final JdbcTemplate jdbcTemplate;

    @Autowired
    public JobCompletionNotificationListener(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * After job is complete,
     * Return and print the 2 teams, and the time
     */
    @Override
    public void afterJob(JobExecution jobExecution) {
        if (jobExecution.getStatus() == BatchStatus.COMPLETED) {
            log.info("!!! JOB FINISHED! Time to verify the results");

            jdbcTemplate.query("SELECT home_team, away_team FROM match",
                    (rs, row) -> "Home Team: "   rs.getString(1)   " Away Team: "
                              rs.getString(2))
                    .forEach(str -> System.out.println(str));
        }
    }
}

Nothing logs in my console when I run the spring boot application. Hopefully this is enough information, I can provide more if needed. Sincerely, thanks for anyone willing to help.

This program is intended to take in a CSV (2022 World Cup information) and imbed it into a HSQL embedded database within a Spring Boot application. I have followed the official Spring Boot guide for completing a Spring Batch Job (here: https://spring.io/guides/gs/batch-processing/ ) and have had no luck so far.

CodePudding user response:

Please, could you check if the method "afterJob" is doing/happening other status as BatchStatus. ABANDONED?

 public void afterJob(JobExecution jobExecution) {
        if (jobExecution.getStatus() == BatchStatus.ABANDONED) {
...

CodePudding user response:

Please try removing the annotation @EnableBatchProcessing.

As you're importing from the jakarta namespace, I assume you're using Spring Boot 3 and Spring Batch 5.

With Spring Boot 3, the Spring Batch annotation @EnableBatchProcessing now has the effect of disabling the auto-configuration for Spring Batch. If you expect that the exposed Job is automatically started on application start-up, then it's this auto-configuration that you're missing.

  • Related