How to get access to job parameters from ItemReader, in Spring Batch?
Asked Answered
H

11

84

This is part of my job.xml:

<job id="foo" job-repository="job-repository">
  <step id="bar">
    <tasklet transaction-manager="transaction-manager">
      <chunk commit-interval="1"
        reader="foo-reader" writer="foo-writer"
      />
    </tasklet>
  </step>
</job>

This is the item reader:

import org.springframework.batch.item.ItemReader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("foo-reader")
public final class MyReader implements ItemReader<MyData> {
  @Override
  public MyData read() throws Exception {
    //...
  }
  @Value("#{jobParameters['fileName']}")
  public void setFileName(final String name) {
    //...
  }
}

This is what Spring Batch is saying in runtime:

Field or property 'jobParameters' cannot be found on object of 
type 'org.springframework.beans.factory.config.BeanExpressionContext'

What's wrong here? Where I can read more about these mechanisms in Spring 3.0?

Hyperphagia answered 20/5, 2011 at 22:3 Comment(0)
B
85

As was stated, your reader needs to be 'step' scoped. You can accomplish this via the @Scope("step") annotation. It should work for you if you add that annotation to your reader, like the following:

import org.springframework.batch.item.ItemReader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("foo-reader")
@Scope("step")
public final class MyReader implements ItemReader<MyData> {
  @Override
  public MyData read() throws Exception {
    //...
  }

  @Value("#{jobParameters['fileName']}")
  public void setFileName(final String name) {
    //...
  }
}

This scope is not available by default, but will be if you are using the batch XML namespace. If you are not, adding the following to your Spring configuration will make the scope available, per the Spring Batch documentation:

<bean class="org.springframework.batch.core.scope.StepScope" />
Bilski answered 28/8, 2011 at 3:21 Comment(5)
StepScope is now available by defaultChary
There's also a shortcut since v2.2 using @StepScope with @BeanAnamariaanamnesis
@Scope("step") didn't work for me, whereas @StepScope didCapstan
Spring doesn't really like final classesGreensboro
we can annotate the @StepScope on methodTrudey
B
35

If you want to define your ItemReader instance and your Step instance in a single JavaConfig class. You can use the @StepScope and the @Value annotations such as:

@Configuration
public class ContributionCardBatchConfiguration {

   private static final String WILL_BE_INJECTED = null;

   @Bean
   @StepScope
   public FlatFileItemReader<ContributionCard> contributionCardReader(@Value("#{jobParameters['fileName']}")String contributionCardCsvFileName){

     ....
   }

   @Bean
   Step ingestContributionCardStep(ItemReader<ContributionCard> reader){
         return stepBuilderFactory.get("ingestContributionCardStep")
                 .<ContributionCard, ContributionCard>chunk(1)
                 .reader(contributionCardReader(WILL_BE_INJECTED))
                 .writer(contributionCardWriter())
                 .build();
    }
}

The trick is to pass a null value to the itemReader since it will be injected through the @Value("#{jobParameters['fileName']}") annotation.

Thanks to Tobias Flohre for his article : Spring Batch 2.2 – JavaConfig Part 2: JobParameters, ExecutionContext and StepScope

Bleareyed answered 4/4, 2017 at 14:18 Comment(1)
How will we test this?Dan
I
21

Pretty late, but you can also do this by annotating a @BeforeStep method:

@BeforeStep
    public void beforeStep(final StepExecution stepExecution) {
        JobParameters parameters = stepExecution.getJobExecution().getJobParameters();
        //use your parameters
}
Inquest answered 5/10, 2016 at 12:8 Comment(3)
This is only useful, if the usage of the job-parameters are not altering the state of the ItemReader. If they are e.g. used to designate what data to read, then concurrent execution of the reader might cause incorrect behaviour.Unbowed
@tveon, this can be solved by using StepScope when declaring the reader bean, right?Chute
@Chute yes, and unless you reuse the reader later in the same job, then you can also solve it with JobScopeUnbowed
B
14

To be able to use the jobParameters I think you need to define your reader as scope 'step', but I am not sure if you can do it using annotations.

Using xml-config it would go like this:

<bean id="foo-readers" scope="step"
  class="...MyReader">
  <property name="fileName" value="#{jobExecutionContext['fileName']}" />
</bean>

See further at the Spring Batch documentation.

Perhaps it works by using @Scope and defining the step scope in your xml-config:

<bean class="org.springframework.batch.core.scope.StepScope" />
Bookcraft answered 23/5, 2011 at 7:53 Comment(2)
I'm interested in annotations specifically. This format of configuration is old and documented.Hyperphagia
You haven't stated that you were interested in an annotations-only solution only. Thanks for the vote anyhow.Bookcraft
O
5

Complement with an additional example, you can access all job parameters in JavaConfig class:

@Bean
@StepScope
public ItemStreamReader<GenericMessage> reader(@Value("#{jobParameters}") Map<String,Object> jobParameters){
          ....
}
Overcareful answered 19/6, 2018 at 2:58 Comment(0)
H
2

While executing the job we need to pass Job parameters as follows:

JobParameters jobParameters= new JobParametersBuilder().addString("file.name", "filename.txt").toJobParameters();   
JobExecution execution = jobLauncher.run(job, jobParameters);  

by using the expression language we can import the value as follows:

 #{jobParameters['file.name']}
Housewarming answered 19/6, 2017 at 1:0 Comment(0)
S
1

'technical' writing.

configuration class.

    @Autowired
    @Qualifier("testReader")
    private testReader reader;


    @Bean(name = "testJob")
    public Job testJob(@Autowired @Qualifier("testStep") Step step) {
        return jobBuilderFactory
                .get("testJob")
                .incrementer(new RunIdIncrementer())
//                .listener(new JobCompletionListener())
                .start(step)
                .build();

    }

    @Bean("testStep")
    @JobScope
    public Step testStep(@Value("#{jobParameters['key']}") String key) {
        return stepBuilderFactory.get("testStep")
                .<UniqueUserVO, List<UniqueUser>>chunk(500)
                .reader(reader.setKey(key).reader())
                .processor(processor.processor())
                .writer(writer.writer())
                .build();

    }

reader interface class.

public interface Reader<T> {

    /**
     * reader 
     * @return
     */
    ItemReader<T> reader();
}

reader class

@Component
public class TestReader implements Reader<UniqueUserVO> {
    private String key;

    public TestReader setKey(String key) {
        this.key= key;
        return this;
    }
    @Override
    public ItemReader<UniqueUserVO> reader() {
       xxxxx
    }
}

Saturnalia answered 5/6, 2023 at 2:37 Comment(0)
H
0

Let's consider a scenario where you need to access JobParameters in an ItemWriter or ItemReader to set values like the file path for writing. In this case, you can utilize a JobParameterExecutionContextCopyListener.

Suppose you have a non-bean class where you create your STEP reader/writer. You can then create a JobParameterExecutionContextCopyListener and specify the keys you want to extract, like myDate:

private TaskletStep internalBuild() {
    try {
        JobParameterExecutionContextCopyListener listener = new JobParameterExecutionContextCopyListener();
        listener.setKeys(new String []{"myDate"});
        return new StepBuilder("MY_STEP", jobRepository)
                .<T, T>chunk(batchSize, transactionManager)
                .reader(itemReader())
                .processor(new PassThroughItemProcessor<>())
                .writer(itemWriter())
                .listener(listener)
                .build();
    } catch (IOException e) {
        throw new SqlToCsvException(e);
    }
}

Next, in your writer (e.g., FlatFileItemWriter), if you want to set the resource based on the date obtained from the JobParameters, you can override the open method to handle this logic:

    public FlatFileItemWriter itemWriter() {
    FlatFileItemWriter writer = new FlatFileItemWriter<T>(){
        // Override the resource given from the executionContext.
        @Override
        public void open(ExecutionContext executionContext) throws ItemStreamException {
            this.setResource(new FileSystemResource(getMyFileNameFromDate((LocalDate) executionContext.get("myDate"))));
            super.open(executionContext);
        }
    };

    // Other configurations for the writer...

    return writer;
}

This approach allows you to dynamically set the resource (file path in this case) based on the JobParameter (e.g., date) provided during the job execution.

Hepato answered 18/3, 2024 at 8:27 Comment(0)
G
-1

This could be an easier manner to do it:

@Configuration
@Setter
@StepScope
public  class Reader extends FlatFileItemReader<Object> {

public Reader(@Value("#{jobParameters['filePath']}") String resource){
    setResource(new FileSystemResource(resource));
   }

}
Gunthar answered 13/6, 2022 at 17:26 Comment(0)
A
-1

You can use StepExecution context inside a method annotated with @BeforeStep annotation inside your item reader to get the Job parameters & set it to a variable, which you can use inside your read method.

In my case I've written something like this :-

@Component
@RequiredArgsConstructor
public class SpelItemReader implements ItemReader<BatchRequest>{

private String requestId;


@Override
public BatchRequest read() {
   //access the request-id variable here
}

@BeforeStep
public void beforeStep(StepExecution stepExecution) {
    requestId = stepExecution.getJobParameters().getString("requestId");
}

}

Auger answered 14/10, 2022 at 8:36 Comment(0)
W
-3

Did you declare the jobparameters as map properly as bean?

Or did you perhaps accidently instantiate a JobParameters object, which has no getter for the filename?

For more on expression language you can find information in Spring documentation here.

Willtrude answered 4/6, 2011 at 9:11 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.