Overview

How to create tasklet model job is explained. Refer to Spring Batch architecture for the architecture of tasklet model.

Components

Tasklet model job does not register multiple components. It only implements org.springframework.batch.core.step.tasklet.Tasklet and sets it in Bean definition. ItemReader and ItemWriter which are components of the chunk model can also be used as components as the advanced implementation means.

HowToUse

How to implement tasklet model job is explained in the following order here.

Job configuration

Define tasklet model job in Bean definition file. An example is shown below.

Example of Bean definition file (Tasklet model)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:batch="http://www.springframework.org/schema/batch"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
             http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd">

    <!-- (1) -->
    <import resource="classpath:META-INF/spring/job-base-context.xml"/>

    <!-- (2) -->
    <context:annotation-config/>

    <!-- (3) -->
    <context:component-scan
          base-package="org.terasoluna.batch.functionaltest.app.common"/>

    <!-- (4) -->
    <batch:job id="jobSimpleJob" job-repository="jobRepository"> <!-- (5) -->
        <batch:step id="simpleJob.step01"> <!-- (6) -->
            <batch:tasklet transaction-manager="jobTransactionManager"
                           ref="simpleJobTasklet"/> <!-- (7) -->
        </batch:step>
    </batch:job>

</beans>
Example of tasklet implementation class
package org.terasoluna.batch.functionaltest.app.common;

@Component // (3)
public class SimpleJobTasklet implements Tasklet {
  // omitted
}
S. No. Explanation

(1)

Import the settings to always read the required Bean definition when using TERASOLUNA Batch 5.x.

(2)

Enable Bean definition using annotation. Use it with (3).

(3)

Set base package of component-scan target. Use it with (2).
In the tasklet model, Bean is defined by annotation however, Bean definition of tasklet implementation class is not required in XML.

(4)

Job configuration.
The value set in the id attribute should be unique in the range of all jobs included in one batch application.

(5)

JobRepository configuration.
The value set in the job-repository attribute should be fixed to jobRepository unless there is a special reason.
This will allow all the jobs to be managed in one JobRepository. Resolve Bean definition of jobRepository by (1).

(6)

Step configuration.
The value set in the id attribute should be unique in one job.
By adding id attribute set in (4) as a prefix if the step is made unique in the range of all jobs included in the batch application same as job, it can be effectively used in various situations such as for log output, identification of error occurrence.
Therefore, it should be <Job id>.<step name>.

(7)

Tasklet configuration.
The value set in the transaction-manager attribute should be fixed to jobTransactionManager unless there is a special reason.
This will manage the processes of the entire tasklet in one transaction. For details, refer to Transaction control.
Resolve Bean definition of jobTransactionManager by (1).

The value to be set in ref attribute is the Bean name resolved in (3).
SimpleJobTasklet, the tasklet implementation class name should be simpleJobTasklet with the first letter in lower case.

Bean name when using annotation

Bean name when using @Component annotation is generated through org.springframework.context.annotation.AnnotationBeanNameGenerator. Refer to Javadoc of this class when you want to confirm the naming rules.

Implementation of tasklet

First, understand the overview with simple implementation, then proceed to implementation using the components of the chunk model.

It is explained in the following order.

Implementation of simple tasklet

Basic points are explained through tasklet implementation only for log output.

Example of simple tasklet implementation class
package org.terasoluna.batch.functionaltest.app.common;

// omitted

@Component
public class SimpleJobTasklet implements Tasklet { // (1)

    private static final Logger logger =
            LoggerFactory.getLogger(SimpleJobTasklet.class);

    @Override
    public RepeatStatus execute(StepContribution contribution,
            ChunkContext chunkContext) throws Exception {  // (2)
        logger.info("called tasklet."); // (3)
        return RepeatStatus.FINISHED; // (4)
    }
}
Sr. No. Explanation

(1)

Implement org.springframework.batch.core.step.tasklet.Tasklet interface using implements.

(2)

Implement execute method to be defined by Tasklet interface. Arguments StepContribution, ChunkContext are used however, they are not explained here.

(3)

Implement any process. INFO log is output here.

(4)

Return whether or not the tasklet process is completed.
Always specify as return RepeatStatus.FINISHED;.

Implementation of tasklet using the components of chunk model

Spring Batch does not mention using various components of chunk model during tasklet implementation. In TERASOLUNA Batch 5.x, you can select this depending on the following situations.

  • When multiple resources are combined and processed, it is difficult to be as per the chunk model format

  • When processes are implemented at various places in the chunk model, tasklet model is better to understand the overall image easily

  • When recovery is made simple and you want to use batch commit of tasklet model instead of intermediate commit of chunk model

The tasklet implementation that uses ItemReader and ItemWriter which are the components of the chunk model is explained below.

Tasklet implementation example that uses the components of chunk model
@Component()
@Scope("step") // (1)
public class SalesPlanChunkTranTask implements Tasklet {

    @Inject
    @Named("detailCSVReader") // (2)
    ItemStreamReader<SalesPlanDetail> itemReader; // (3)

    @Inject
    SalesPlanDetailRepository repository; // (4)

    @Override
    public RepeatStatus execute(StepContribution contribution,
            ChunkContext chunkContext) throws Exception {

        SalesPlanDetail item;

        try {
            itemReader.open(chunkContext.getStepContext().getStepExecution()
                    .getExecutionContext()); // (5)

            while ((item = itemReader.read()) != null) { // (6)

                // do some processes.

                repository.create(item); // (7)
            }
        } finally {
            itemReader.close(); // (8)
        }
        return RepeatStatus.FINISHED;
    }
}
Bean definition example 1
<!-- omitted -->
<import resource="classpath:META-INF/spring/job-base-context.xml"/>

<context:annotation-config/>

<context:component-scan
    base-package="org.terasoluna.batch.functionaltest.app.plan" />
<context:component-scan
    base-package="org.terasoluna.batch.functionaltest.ch05.transaction.component" />

<!-- (9) -->
<mybatis:scan
    base-package="org.terasoluna.batch.functionaltest.app.repository.plan"
    factory-ref="jobSqlSessionFactory"/>

<!-- (10) -->
<bean id="detailCSVReader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}">
    <property name="lineMapper">
        <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
            <property name="lineTokenizer">
                <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
                      p:names="branchId,year,month,customerId,amount"/>
            </property>
            <property name="fieldSetMapper">
                <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
                      p:targetType="org.terasoluna.batch.functionaltest.app.model.plan.SalesPlanDetail"/>
            </property>
        </bean>
    </property>
</bean>

<!-- (11) -->
<batch:job id="createSalesPlanChunkTranTask" job-repository="jobRepository">
    <batch:step id="createSalesPlanChunkTranTask.step01">
        <batch:tasklet transaction-manager="jobTransactionManager"
                       ref="salesPlanChunkTranTask"/>
    </batch:step>
</batch:job>
Sr. No. Explanation

(1)

Set the same step scope as the Bean scope of ItemReader to be used in this class.

(2)

Access input resources (flat files in this example) through ItemReader.
Specify Bean name as detailCSVReader but it is optional for clarity purpose.

(3)

Define the type as ItemStreamReader that is sub-interface of ItemReader.
This is because it is necessary to open/close the resource of (5), (8). It is supplemented later.

(4)

Access output resources (database in this example) through Mapper of MyBatis.
Mapper is directly used for the sake of simplicity. There is no need to always use ItemWriter. Of course, MyBatisBatchItemWriter can be used.

(5)

Open input resource.

(6)

Loop all input resources sequentially.
ItemReader#read returns null when it reads all the input data and reaches the end.

(7)

Output to database.

(8)

Resource should be closed without fail.
Exception handling should be implemented. When an exception occurs, the transactions of the entire tasklet are rolled-backed, stack trace of exception is output and the job terminates abnormally.

(9)

To output to database, add the settings of mybatis:scan. The details are not explained here.

(10)

To enter from file, add Bean definition of FlatFileItemReader. The details are not explained here.

(11)

Since all the components are resolved by annotation,
it is same as Implementation of simple tasklet.

On unification of scope

The scope of tasklet implementation class and Bean to be Injected should have the same scope.

For example, if FlatFileItemReader receives an input file path from an argument, the Bean scope should be step. In this case, the scope of tasklet implementation class should also be step.

If the scope of tasklet implementation class is set to singleton temporarily, after instantiating the tasklet implementation class at the time of generating ApplicationContext at application startup if it tries to Inject by resolving the instance of FlatFileItemReader, FlatFileItemReader will be in the step scope, however will not exist yet. because it is to be generated at the time of step execution. As the result, the tasklet implementation class cannot be instantiated and fails to generate ApplicationContext.

Regarding the type of field assigned with @Inject

Any one of the following type depending on the implementation class to be used.

  • ItemReader/ItemWriter

    • Used when there is no need to open/close the target resource.

  • ItemSteamReader/ItemStreamWriter

    • Used when there is a need to open/close the target resource.

It should be decided which type to use after confirming javadoc. Typical examples are shown below.

In case of FlatFileItemReader/Writer

handle by ItemSteamReader/ItemStreamWriter

In case of MyBatisCursorItemReader

handle by ItemStreamReader

In case of MyBatisBatchItemWriter

handle by ItemWriter

Another example is shown when ItemReader and ItemWriter are used at the same time.

Tasklet implementation example 2 that uses the components of chunk model
@Component
@Scope("step")
public class SalesPerformanceTasklet implements Tasklet {


    @Inject
    ItemStreamReader<SalesPerformanceDetail> reader;

    @Inject
    ItemWriter<SalesPerformanceDetail> writer; // (1)

    int chunkSize = 10; // (2)

    @Override
    public RepeatStatus execute(StepContribution contribution,
            ChunkContext chunkContext) throws Exception {

        try {
            reader.open(chunkContext.getStepContext().getStepExecution()
                    .getExecutionContext());

            List<SalesPerformanceDetail> items = new ArrayList<>(chunkSize); // (2)
            SalesPerformanceDetail item = null;
            do {
                // Pseudo operation of ItemReader
                for (int i = 0; i < chunkSize; i++) { // (3)
                    item = reader.read();
                    if (item == null) {
                        break;
                    }
                    // Pseudo operation of ItemProcessor
                    // do some processes.

                    items.add(item);
                }

                // Pseudo operation of ItemWriter
                if (!items.isEmpty()) {
                    writer.write(items); // (4)
                    items.clear();
                }
            } while (item != null);
        } finally {
            try {
                reader.close();
            } catch (Exception e) {
                // do nothing.
            }
        }

        return RepeatStatus.FINISHED;
    }
}
Bean definition example 2
<!-- omitted -->
<import resource="classpath:META-INF/spring/job-base-context.xml"/>

<context:annotation-config/>
<context:component-scan
    base-package="org.terasoluna.batch.functionaltest.app.common,
        org.terasoluna.batch.functionaltest.app.performance,
        org.terasoluna.batch.functionaltest.ch06.exceptionhandling"/>
<mybatis:scan
    base-package="org.terasoluna.batch.functionaltest.app.repository.performance"
    factory-ref="jobSqlSessionFactory"/>

<bean id="detailCSVReader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}">
    <property name="lineMapper">
        <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
            <property name="lineTokenizer">
                <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
                      p:names="branchId,year,month,customerId,amount"/>
            </property>
            <property name="fieldSetMapper">
                <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
                      p:targetType="org.terasoluna.batch.functionaltest.app.model.performance.SalesPerformanceDetail"/>
            </property>
        </bean>
    </property>
</bean>

<!-- (1) -->
<bean id="detailWriter"
      class="org.mybatis.spring.batch.MyBatisBatchItemWriter"
      p:statementId="org.terasoluna.batch.functionaltest.app.repository.performance.SalesPerformanceDetailRepository.create"
      p:sqlSessionTemplate-ref="batchModeSqlSessionTemplate"/>


<batch:job id="jobSalesPerfTasklet" job-repository="jobRepository">
    <batch:step id="jobSalesPerfTasklet.step01">
        <batch:tasklet ref="salesPerformanceTasklet"
                       transaction-manager="jobTransactionManager"/>
    </batch:step>
</batch:job>
Sr. No. Explanation

(1)

Use MyBatisBatchItemWriter as the implementation of ItemWriter.

(2)

ItemWriter outputs a fixed number of records collectively.
It processes and output 10 records each.

(3)

As per the behavior of chunk model,
it should be read→process→read→process→…​→write.

(4)

Output through ItemWriter collectively.

Decide each time whether to use the implementation class of ItemReader or ItemWriter. For file access, the implementation class of ItemReader and ItemWriter can be used. For other than this such as database access, there is no need to use compulsorily. It can be used to improve performance.