SpringBatch使用(一)

本文介绍SpringBatch框架的核心概念及其实现批量数据处理的方法。包括Job、Step等关键组件的定义,以及如何通过ItemReader、ItemProcessor和ItemWriter实现数据的读取、处理和写入。还展示了具体的代码示例和配置。
部署运行你感兴趣的模型镜像

一、SpringBatch简介 

      1、Spring Batch是一个轻量级,全面的批处理框架,旨在开发对企业系统日常运营至关重要的强大批处理应用程序。Spring Batch构建了人们期望的Spring Framework特性(生产力,基于POJO的开发方法和一般易用性),同时使开发人员可以在必要时轻松访问和利用更高级的企业服务。

      2、中文社区

二、SpringBatch核心概念

      1、 任务Job相关:

      是封装整个批处理过程的单位,跑一个批处理任务,就是跑一个Job所定义的内容

      ①、Job:封装处理实体,定义过程逻辑。

      ②、JobInstanceJob的运行实例,不同的实例,参数不同,所以定义好一个Job后可以通过不同参数运行多次。

      ③、JobParameters:与JobInstance相关联的参数。

      ④、JobExecution:代表Job的一次实际执行,可能成功、可能失败。

    2、Step:

    Step是对Job某个过程的封装,一个Job可以包含一个或多个Step,一步步的Step按特定逻辑执行,才代表Job执行完成 。 

     定义一个Job关键是定义好一个或多个Step,然后把它们组装好即可。而定义Step有多种方法,但有一种常用的模型就是输入——处理——输出,即Item ReaderItem ProcessorItem Writer。比如通过Item Reader从文件输入数据,然后通过Item Processor进行业务处理和数据转换,最后通过Item Writer写到数据库中去。

ItemReader

ItemProcessor 

 ItemWriter

                                                                                     

                                                             Job任务图

3、JobRepository

  对整个批处理的新增、更新、执行进行记录。

package org.springframework.batch.core.repository;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.lang.Nullable;
import org.springframework.transaction.annotation.Isolation;

import java.util.Collection;

/**
 * <p>
 * Repository responsible for persistence of batch meta-data entities.
 * </p>
 *
 * @see JobInstance
 * @see JobExecution
 * @see StepExecution
 *
 * @author Lucas Ward
 * @author Dave Syer
 * @author Robert Kasanicky
 * @author David Turanski
 * @author Michael Minella
 * @author Mahmoud Ben Hassine
 */
public interface JobRepository {

	/**
	 * Check if an instance of this job already exists with the parameters
	 * provided.
	 *
	 * @param jobName the name of the job
	 * @param jobParameters the parameters to match
	 * @return true if a {@link JobInstance} already exists for this job name
	 * and job parameters
	 */
	boolean isJobInstanceExists(String jobName, JobParameters jobParameters);

	/**
	 * Create a new {@link JobInstance} with the name and job parameters provided.
	 *
	 * @param jobName logical name of the job
	 * @param jobParameters parameters used to execute the job
	 * @return the new {@link JobInstance}
	 */
	JobInstance createJobInstance(String jobName, JobParameters jobParameters);

	/**
	 * Create a new {@link JobExecution} based upon the {@link JobInstance} it's associated
	 * with, the {@link JobParameters} used to execute it with and the location of the configuration
	 * file that defines the job.
	 *
	 * @param jobInstance {@link JobInstance} instance to initialize the new JobExecution.
	 * @param jobParameters {@link JobParameters} instance to initialize the new JobExecution.
	 * @param jobConfigurationLocation {@link String} instance to initialize the new JobExecution.
	 * @return the new {@link JobExecution}.
	 */
	JobExecution createJobExecution(JobInstance jobInstance, JobParameters jobParameters, String jobConfigurationLocation);

	/**
	 * <p>
	 * Create a {@link JobExecution} for a given {@link Job} and
	 * {@link JobParameters}. If matching {@link JobInstance} already exists,
	 * the job must be restartable and it's last JobExecution must *not* be
	 * completed. If matching {@link JobInstance} does not exist yet it will be
	 * created.
	 * </p>
	 *
	 * <p>
	 * If this method is run in a transaction (as it normally would be) with
	 * isolation level at {@link Isolation#REPEATABLE_READ} or better, then this
	 * method should block if another transaction is already executing it (for
	 * the same {@link JobParameters} and job name). The first transaction to
	 * complete in this scenario obtains a valid {@link JobExecution}, and
	 * others throw {@link JobExecutionAlreadyRunningException} (or timeout).
	 * There are no such guarantees if the {@link JobInstanceDao} and
	 * {@link JobExecutionDao} do not respect the transaction isolation levels
	 * (e.g. if using a non-relational data-store, or if the platform does not
	 * support the higher isolation levels).
	 * </p>
	 *
	 * @param jobName the name of the job that is to be executed
	 *
	 * @param jobParameters the runtime parameters for the job
	 *
	 * @return a valid {@link JobExecution} for the arguments provided
	 *
	 * @throws JobExecutionAlreadyRunningException if there is a
	 * {@link JobExecution} already running for the job instance with the
	 * provided job and parameters.
	 * @throws JobRestartException if one or more existing {@link JobInstance}s
	 * is found with the same parameters and {@link Job#isRestartable()} is
	 * false.
	 * @throws JobInstanceAlreadyCompleteException if a {@link JobInstance} is
	 * found and was already completed successfully.
	 *
	 */
	JobExecution createJobExecution(String jobName, JobParameters jobParameters)
			throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException;

	/**
	 * Update the {@link JobExecution} (but not its {@link ExecutionContext}).
	 *
	 * Preconditions: {@link JobExecution} must contain a valid
	 * {@link JobInstance} and be saved (have an id assigned).
	 *
	 * @param jobExecution {@link JobExecution} instance to be updated in the repo.
	 */
	void update(JobExecution jobExecution);

	/**
	 * Save the {@link StepExecution} and its {@link ExecutionContext}. ID will
	 * be assigned - it is not permitted that an ID be assigned before calling
	 * this method. Instead, it should be left blank, to be assigned by a
	 * {@link JobRepository}.
	 *
	 * Preconditions: {@link StepExecution} must have a valid {@link Step}.
	 *
	 * @param stepExecution {@link StepExecution} instance to be added to the repo.
	 */
	void add(StepExecution stepExecution);

	/**
	 * Save a collection of {@link StepExecution}s and each {@link ExecutionContext}. The
	 * StepExecution ID will be assigned - it is not permitted that an ID be assigned before calling
	 * this method. Instead, it should be left blank, to be assigned by {@link JobRepository}.
	 *
	 * Preconditions: {@link StepExecution} must have a valid {@link Step}.
	 *
	 * @param stepExecutions collection of {@link StepExecution} instances to be added to the repo.
	 */
	void addAll(Collection<StepExecution> stepExecutions);

	/**
	 * Update the {@link StepExecution} (but not its {@link ExecutionContext}).
	 *
	 * Preconditions: {@link StepExecution} must be saved (have an id assigned).
	 *
	 * @param stepExecution {@link StepExecution} instance to be updated in the repo.
	 */
	void update(StepExecution stepExecution);

	/**
	 * Persist the updated {@link ExecutionContext}s of the given
	 * {@link StepExecution}.
	 *
	 * @param stepExecution {@link StepExecution} instance to be used to update the context.
	 */
	void updateExecutionContext(StepExecution stepExecution);

	/**
	 * Persist the updated {@link ExecutionContext} of the given
	 * {@link JobExecution}.
	 * @param jobExecution {@link JobExecution} instance to be used to update the context.
	 */
	void updateExecutionContext(JobExecution jobExecution);

	/**
	 * @param jobInstance {@link JobInstance} instance containing the step executions.
	 * @param stepName the name of the step execution that might have run.
	 * @return the last execution of step for the given job instance.
	 */
	@Nullable
	StepExecution getLastStepExecution(JobInstance jobInstance, String stepName);

	/**
	 * @param jobInstance {@link JobInstance} instance containing the step executions.
	 * @param stepName the name of the step execution that might have run.
	 * @return the execution count of the step within the given job instance.
	 */
	int getStepExecutionCount(JobInstance jobInstance, String stepName);

	/**
	 * @param jobName the name of the job that might have run
	 * @param jobParameters parameters identifying the {@link JobInstance}
	 * @return the last execution of job if exists, null otherwise
	 */
	@Nullable
	JobExecution getLastJobExecution(String jobName, JobParameters jobParameters);

}

  4、JobLauncher

 负责启动任Job

 三、使用案例

    1、引入依赖

        <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>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        

   2、引入官方建表SQL

-- Autogenerated: do not edit this file

CREATE TABLE BATCH_JOB_INSTANCE  (
	JOB_INSTANCE_ID BIGINT  NOT NULL PRIMARY KEY ,
	VERSION BIGINT ,
	JOB_NAME VARCHAR(100) NOT NULL,
	JOB_KEY VARCHAR(32) NOT NULL,
	constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY)
) ENGINE=InnoDB;

CREATE TABLE BATCH_JOB_EXECUTION  (
	JOB_EXECUTION_ID BIGINT  NOT NULL PRIMARY KEY ,
	VERSION BIGINT  ,
	JOB_INSTANCE_ID BIGINT NOT NULL,
	CREATE_TIME DATETIME(6) NOT NULL,
	START_TIME DATETIME(6) DEFAULT NULL ,
	END_TIME DATETIME(6) DEFAULT NULL ,
	STATUS VARCHAR(10) ,
	EXIT_CODE VARCHAR(2500) ,
	EXIT_MESSAGE VARCHAR(2500) ,
	LAST_UPDATED DATETIME(6),
	JOB_CONFIGURATION_LOCATION VARCHAR(2500) NULL,
	constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID)
	references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID)
) ENGINE=InnoDB;

CREATE TABLE BATCH_JOB_EXECUTION_PARAMS  (
	JOB_EXECUTION_ID BIGINT NOT NULL ,
	TYPE_CD VARCHAR(6) NOT NULL ,
	KEY_NAME VARCHAR(100) NOT NULL ,
	STRING_VAL VARCHAR(250) ,
	DATE_VAL DATETIME(6) DEFAULT NULL ,
	LONG_VAL BIGINT ,
	DOUBLE_VAL DOUBLE PRECISION ,
	IDENTIFYING CHAR(1) NOT NULL ,
	constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ENGINE=InnoDB;

CREATE TABLE BATCH_STEP_EXECUTION  (
	STEP_EXECUTION_ID BIGINT  NOT NULL PRIMARY KEY ,
	VERSION BIGINT NOT NULL,
	STEP_NAME VARCHAR(100) NOT NULL,
	JOB_EXECUTION_ID BIGINT NOT NULL,
	START_TIME DATETIME(6) NOT NULL ,
	END_TIME DATETIME(6) DEFAULT NULL ,
	STATUS VARCHAR(10) ,
	COMMIT_COUNT BIGINT ,
	READ_COUNT BIGINT ,
	FILTER_COUNT BIGINT ,
	WRITE_COUNT BIGINT ,
	READ_SKIP_COUNT BIGINT ,
	WRITE_SKIP_COUNT BIGINT ,
	PROCESS_SKIP_COUNT BIGINT ,
	ROLLBACK_COUNT BIGINT ,
	EXIT_CODE VARCHAR(2500) ,
	EXIT_MESSAGE VARCHAR(2500) ,
	LAST_UPDATED DATETIME(6),
	constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ENGINE=InnoDB;

CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT  (
	STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
	SHORT_CONTEXT VARCHAR(2500) NOT NULL,
	SERIALIZED_CONTEXT TEXT ,
	constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID)
	references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID)
) ENGINE=InnoDB;

CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT  (
	JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
	SHORT_CONTEXT VARCHAR(2500) NOT NULL,
	SERIALIZED_CONTEXT TEXT ,
	constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ENGINE=InnoDB;

CREATE TABLE BATCH_STEP_EXECUTION_SEQ (
	ID BIGINT NOT NULL,
	UNIQUE_KEY CHAR(1) NOT NULL,
	constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_STEP_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_STEP_EXECUTION_SEQ);

CREATE TABLE BATCH_JOB_EXECUTION_SEQ (
	ID BIGINT NOT NULL,
	UNIQUE_KEY CHAR(1) NOT NULL,
	constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_JOB_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_EXECUTION_SEQ);

CREATE TABLE BATCH_JOB_SEQ (
	ID BIGINT NOT NULL,
	UNIQUE_KEY CHAR(1) NOT NULL,
	constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_JOB_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_SEQ);

  业务数据SQL 

CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `age` int(11) NOT NULL,
  `sex` varchar(20) NOT NULL,
  `address` varchar(100) NOT NULL,
  `cid` int(11) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;


INSERT INTO `student`(NAME,age,sex,address,cid) VALUE('Tom',23,'男','湖南',23);

INSERT INTO `student`(NAME,age,sex,address,cid) VALUE('Luck',24,'男','湖北',24);

INSERT INTO `student`(NAME,age,sex,address,cid) VALUE('Jack',25,'男','武汉',25);

INSERT INTO `student`(NAME,age,sex,address,cid) VALUE('User',26,'男','陕西',26);

 3、业务代码

  ①、DataBatchJob:读取数据任务
package com.boot.skywalk.job;

import com.boot.skywalk.entity.Student;
import com.boot.skywalk.listener.JobListener;
import lombok.extern.slf4j.Slf4j;
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.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.database.JpaPagingItemReader;
import org.springframework.batch.item.database.orm.JpaNativeQueryProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.persistence.EntityManagerFactory;

@Slf4j
@Component
public class DataBatchJob {
    /**
     * Job构建工厂,用于构建Job
     */
    private final JobBuilderFactory jobBuilderFactory;

    /**
     * Step构建工厂,用于构建Step
     */
    private final StepBuilderFactory stepBuilderFactory;

    /**
     * 实体类管理工工厂,用于访问表格数据
     */
    private final EntityManagerFactory emf;

    /**
     * 自定义的简单Job监听器
     */
    private final JobListener jobListener;

    /**
     * 构造器注入
     * @param jobBuilderFactory
     * @param stepBuilderFactory
     * @param emf
     * @param jobListener
     */
    @Autowired
    public DataBatchJob(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory,
                        EntityManagerFactory emf, JobListener jobListener) {
        this.jobBuilderFactory = jobBuilderFactory;
        this.stepBuilderFactory = stepBuilderFactory;
        this.emf = emf;
        this.jobListener = jobListener;
    }

    /**
     * 一个最基础的Job通常由一个或者多个Step组成
     */
    public Job dataHandleJob() {
        return jobBuilderFactory.get("dataHandleJob").
                incrementer(new RunIdIncrementer()).
                // start是JOB执行的第一个step
                        start(handleDataStep()).
                // 可以调用next方法设置其他的step,例如:
                // next(xxxStep()).
                // next(xxxStep()).
                // ...
                // 设置我们自定义的JobListener
                        listener(jobListener).
                build();
    }

    /**
     * 一个简单基础的Step主要分为三个部分
     * ItemReader : 用于读取数据
     * ItemProcessor : 用于处理数据
     * ItemWriter : 用于写数据
     */
    private Step handleDataStep() {
        return stepBuilderFactory.get("getData").
                // <输入对象, 输出对象>  chunk通俗的讲类似于SQL的commit; 这里表示处理(processor)100条后写入(writer)一次
                        <Student, Student>chunk(100).
                // 捕捉到异常就重试,重试100次还是异常,JOB就停止并标志失败
                        faultTolerant().retryLimit(3).retry(Exception.class).skipLimit(100).skip(Exception.class).
                // 指定ItemReader对象
                        reader(getDataReader()).
                // 指定ItemProcessor对象
                        processor(getDataProcessor()).
                // 指定ItemWriter对象
                        writer(getDataWriter()).
                build();
    }

    /**
     * 读取数据
     *
     * @return ItemReader Object
     */
    private ItemReader<? extends Student> getDataReader() {
        // 读取数据,这里可以用JPA,JDBC,JMS 等方式读取数据
        JpaPagingItemReader<Student> reader = new JpaPagingItemReader<>();
        try {
            // 这里选择JPA方式读取数据
            JpaNativeQueryProvider<Student> queryProvider = new JpaNativeQueryProvider<>();
            // 一个简单的 native SQL
            queryProvider.setSqlQuery("SELECT * FROM student");
            // 设置实体类
            queryProvider.setEntityClass(Student.class);
            queryProvider.afterPropertiesSet();
            reader.setEntityManagerFactory(emf);
            // 设置每页读取的记录数
            reader.setPageSize(3);
            // 设置数据提供者
            reader.setQueryProvider(queryProvider);
            reader.afterPropertiesSet();

            // 所有ItemReader和ItemWriter实现都会在ExecutionContext提交之前将其当前状态存储在其中,
            // 如果不希望这样做,可以设置setSaveState(false)
            reader.setSaveState(true);
        } catch (Exception e) {
            log.error("read data error",e);
        }
        return reader;
    }

    /**
     * 处理数据
     *
     * @return ItemProcessor Object
     */
    private ItemProcessor<Student, Student> getDataProcessor() {
        return student -> {
            // 模拟处理数据,这里处理就是打印一下
            log.info("processor data : " + student.toString());
            return student;
        };
    }

    /**
     * 写入数据
     *
     * @return ItemWriter Object
     */
    private ItemWriter<Student> getDataWriter() {
        return list -> {
            for (Student student : list) {
                // 模拟写数据,为了演示的简单就不写入数据库了
                log.info("write data : " + student);
            }
        };
    }
}

  线程池配置

  ②、ExecutorConfiguration

package com.boot.skywalk.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
public class ExecutorConfiguration {
    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(10);
        threadPoolTaskExecutor.setMaxPoolSize(20);
        threadPoolTaskExecutor.setQueueCapacity(500);
        threadPoolTaskExecutor.setThreadNamePrefix("Data-Job");
        return threadPoolTaskExecutor;
    }
}

  ③、监听器:任务执行前后做相关统计.

package com.boot.skywalk.listener;

import lombok.extern.slf4j.Slf4j;
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.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class JobListener implements JobExecutionListener {
    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
    private long startTime;

    @Autowired
    public JobListener(ThreadPoolTaskExecutor threadPoolTaskExecutor){
        this.threadPoolTaskExecutor=threadPoolTaskExecutor;
    }

    /**
     * 该方法会在job开始前执行
     */
    @Override
    public void beforeJob(JobExecution jobExecution) {
        startTime = System.currentTimeMillis();
        log.info("job before " + jobExecution.getJobParameters());
    }

    /**
     * 该方法会在job结束后执行
     */
    @Override
    public void afterJob(JobExecution jobExecution) {
        log.info("JOB STATUS : {}", jobExecution.getStatus());
        if (jobExecution.getStatus() == BatchStatus.COMPLETED) {
            log.info("JOB FINISHED");
            threadPoolTaskExecutor.destroy();
        } else if (jobExecution.getStatus() == BatchStatus.FAILED) {
            log.info("JOB FAILED");
        }
        log.info("Job Cost Time : {}/ms", (System.currentTimeMillis() - startTime));
    }
}
 定时任务执行job,可以使用XXL-JOB、Quartz来执行,这里使用Spring Task.
 ④、BatchTask
package com.boot.skywalk.task;

import com.boot.skywalk.job.DataBatchJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class BatchTask {
    @Autowired
    private JobLauncher jobLauncher;
    @Autowired
    private DataBatchJob dataBatchJob;

    @Scheduled(cron = "0 */1 * * * ?")
    public void runBatch() throws JobParametersInvalidException, JobExecutionAlreadyRunningException,
            JobRestartException, JobInstanceAlreadyCompleteException {
        log.info("定时任务执行了...");
        // 在运行一个job的时候需要添加至少一个参数,这个参数最后会被写到batch_job_execution_params表中,
        // 不添加这个参数的话,job不会运行,并且这个参数在表中中不能重复,若设置的参数已存在表中,则会抛出异常,
        // 所以这里才使用时间戳作为参数
        JobParameters jobParameters = new JobParametersBuilder()
                .addLong("timestamp", System.currentTimeMillis())
                .toJobParameters();

        // 获取job并运行
        Job job = dataBatchJob.dataHandleJob();
        JobExecution execution = jobLauncher.run(job, jobParameters);
        log.info("定时任务结束. Exit Status : {}", execution.getStatus());
    }
}

 ⑤、application中添加如下配置. 

spring:
  datasource:
    # SpringBoot2.x默认是Hikari,无需加下面一行
    type: com.zaxxer.hikari.HikariDataSource
    username: root
    password: XXXXXXXX
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
    driver-class-name: com.mysql.jdbc.Driver
  # jpa模式
  jpa:
    open-in-view: true
    show-sql: true
    hibernate:
      ddl-auto: update
      database: mysql
  # batch
  # 禁止项目启动时运行job
  batch:
    job:
      enabled: false

⑥、启动类开启Spring Batch、Spring Task

  @EnableScheduling

  @EnableBatchProcessing

运行项目如下:

 执行结果表记录

 

四、扩展,监听读取数据、处理数据、写入数据

1、ItemReaderListener

2、ItemProcesserListener

 3、ItemWriteListener

Spring Batch为我们提供了许多开箱即用的ReaderWriter,满足众多业务开发场景。

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

<think>首先,用户的问题是:“我想了解如何使用Spring Batch进行批处理任务开发 请问Spring Batch 使用教程 批处理任务开发”。这翻译成英文是:“I want to learn how to use Spring Batch for batch processing task development. Can you provide a tutorial on Spring Batch batch processing task development?” 根据系统级指令,我需要: 1. 所有行内数学表达式使用$...$格式。 2. 独立公式使用$$...$$格式并单独成段。 3. LaTeX语法正确。 4. 使用中文回答。 5. 生成相关问题。 6. 回答中引用的段落末尾自然地添加引用标识,例如:[^1]。 用户可见层指令: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 - 参考提供的引用,但用户的问题是关于Spring Batch的,所以我需要基于我的知识来回答,因为引用是关于批处理的,但具体到Spring Batch教程。 提供的引用: - [^1]: 介绍了批处理的基本概念和Spring Batch。 - [^2]: 讨论了JavaSpring Boot结合Spring Batch实现批处理任务。 - [^3]: 解释了Spring Batch的核心概念。 我的回答应该基于这些引用,但用户要求的是教程,所以我需要提供个结构化的指南。 结构清晰的回答: - 逐步介绍如何使用Spring Batch开发批处理任务。 - 包括关键概念、设置、代码示例等。 - 使用中文。 生成相关问题:在回答结束后,添加“相关问题”部分。 回答中引用的段落:如果我引用提供的引用,需要在末尾添加引用标识,如[^1]。但用户说“上述引用仅供参考”,所以我应该主要基于我的知识,但可以引用这些内容。 关键点从引用中: - Spring Batch是用于批处理的框架。 - 核心组件:Job、Step、ItemReader、ItemProcessor、ItemWriter。 - 使用Spring Boot简化开发。 我的回答大纲: 1. 介绍Spring Batch:简要解释什么是Spring Batch及其优势。 2. 核心概念:解释Job、Step、ItemReader、ItemProcessor、ItemWriter。 3. 开发步骤: - 设置环境(使用Spring Boot)。 - 定义Job。 - 定义Step。 - 实现Reader、Processor、Writer。 4. 代码示例:提供个简单的批处理任务示例。 5. 运行和测试。 6. 结束语。 确保语言是中文。 生成相关问题:在最后添加“相关问题”部分,列出3-4个相关问题。 数学表达式:用户的问题是编程相关的,可能不需要数学,但如果有,比如在解释算法时,使用$...$或$$...$$。但这里可能不涉及,所以我可以忽略。 现在,构建回答: - 开头:直接回应问题。 - 主体:分步骤解释。 - 结尾:相关问题。 代码示例:使用代码块,如示例中所示。 引用:如果我从提供的引用中提取信息,添加引用标识。例如,当提到核心概念时,可以引用[^3]。 最终输出: - 回答内容。 - 相关问题。</think>### Spring Batch 批处理任务开发教程 Spring Batch个强大的开源框架,专门用于开发企业级批处理应用。它简化了处理大批量数据(如数据迁移、报表生成、数据清洗)的任务,通过提供标准化的组件和分块处理机制来优化性能[^3]。下面,我将以结构化的方式,逐步指导您如何使用 Spring Batch 开发批处理任务。本教程基于 Spring Boot 集成,以简化配置和部署(参考引用[^2])。整个过程包括环境设置、核心概念定义、代码实现和运行测试。 #### 1. **环境设置与项目初始化** 首先,确保您已安装 Java 开发环境(推荐 JDK 11+)。我们将使用 Spring Boot 来快速搭建项目: - 创建个新的 Spring Boot 项目(例如,通过 [Spring Initializr](https://start.spring.io/))。 - 添加依赖:选择 Spring BatchSpring Boot Starter 依赖(如 Spring Boot Starter Batch)。 - Maven 依赖示例: ```xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-batch</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> ``` - 初始化项目后,确保 `application.properties` 文件中配置了数据库(Spring Batch 需要数据库存储作业元数据),例如使用 H2 内存数据库: ```properties spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.h2.console.enabled=true ``` #### 2. **理解 Spring Batch 核心概念** Spring Batch 的核心组件基于分块处理模型(Chunk Processing),这能高效处理大容量数据[^3]。关键概念包括: - **Job(作业)**:个完整的批处理任务,由多个 Step 组成。例如,个数据导入作业可能包含读取、处理和写入步骤。 - **Step(步骤)**:Job 的子单元,每个 Step 定义了个处理阶段。Step 可以是 Tasklet(简单任务)或基于 Chunk 的(分块处理)。 - **ItemReader(读取器)**:从数据源读取数据(如文件、数据库)。 - **ItemProcessor(处理器)**:对读取的数据进行业务逻辑处理(如数据转换、验证)。 - **ItemWriter(写入器)**:将处理后的数据写入目标(如数据库、文件)。 - **Chunk(分块)**:Spring Batch 将数据分成小批次处理(例如,每次处理 100 条记录),以优化内存和性能[^3]。 #### 3. **开发个简单的批处理任务示例** 我们将创建个简单的批处理任务:从 CSV 文件读取用户数据,转换为大写,然后写入数据库。以下是完整代码实现。 **步骤 1: 定义数据模型** 创建个用户实体类(例如,`User.java`): ```java public class User { private String name; private String email; // 构造器、Getter 和 Setter public User(String name, String email) { this.name = name; this.email = email; } // 省略 Getter 和 Setter 方法 } ``` **步骤 2: 实现 ItemReader、ItemProcessor 和 ItemWriter** - **ItemReader**:使用 `FlatFileItemReader` 从 CSV 文件读取数据。 - **ItemProcessor**:实现个简单的处理器,将用户名转为大写。 - **ItemWriter**:使用 `JdbcBatchItemWriter` 写入数据库。 在 Spring Boot 配置类中定义这些组件(例如,`BatchConfig.java`): ```java 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.mapping.BeanWrapperFieldSetMapper; import org.springframework.batch.item.file.mapping.DefaultLineMapper; import org.springframework.batch.item.file.transform.DelimitedLineTokenizer; import org.springframework.batch.item.database.JdbcBatchItemWriter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import javax.sql.DataSource; @Configuration @EnableBatchProcessing public class BatchConfig { @Autowired private JobBuilderFactory jobBuilderFactory; @Autowired private StepBuilderFactory stepBuilderFactory; @Autowired private DataSource dataSource; // ItemReader: 从 CSV 文件读取用户数据 @Bean public ItemReader<User> reader() { FlatFileItemReader<User> reader = new FlatFileItemReader<>(); reader.setResource(new ClassPathResource("users.csv")); // CSV 文件路径 reader.setLineMapper(new DefaultLineMapper<User>() {{ setLineTokenizer(new DelimitedLineTokenizer() {{ setNames("name", "email"); // 对应 CSV 列名 }}); setFieldSetMapper(new BeanWrapperFieldSetMapper<User>() {{ setTargetType(User.class); }}); }}); return reader; } // ItemProcessor: 处理数据(用户名转大写) @Bean public ItemProcessor<User, User> processor() { return user -> { user.setName(user.getName().toUpperCase()); return user; }; } // ItemWriter: 写入数据库 @Bean public ItemWriter<User> writer() { JdbcBatchItemWriter<User> writer = new JdbcBatchItemWriter<>(); writer.setDataSource(dataSource); writer.setSql("INSERT INTO users (name, email) VALUES (:name, :email)"); writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()); return writer; } // 定义 Step: 使用 Chunk 机制(每 10 条记录处理次) @Bean public Step step1() { return stepBuilderFactory.get("step1") .<User, User>chunk(10) // 分块大小 .reader(reader()) .processor(processor()) .writer(writer()) .build(); } // 定义 Job: 包含 Step @Bean public Job importUserJob() { return jobBuilderFactory.get("importUserJob") .start(step1()) .build(); } } ``` **步骤 3: 准备数据文件** 在项目的 `src/main/resources` 目录下创建 `users.csv` 文件,内容示例: ``` John,john@example.com Jane,jane@example.com ``` **步骤 4: 创建数据库表** 确保数据库中有 `users` 表(例如,在 H2 控制台中执行 SQL): ```sql CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255) ); ``` #### 4. **运行和测试批处理任务** - **运行任务**:Spring Boot 会自动启动批处理作业。启动应用后,Job 会执行。您可以在控制台查看日志,例如: ``` Started importUserJob in 2.5 seconds Processed 2 records ``` - **测试**:使用单元测试验证。创建个测试类: ```java import org.junit.jupiter.api.Test; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class BatchTest { @Autowired private JobLauncher jobLauncher; @Autowired private Job importUserJob; @Test public void testBatchJob() throws Exception { JobExecution execution = jobLauncher.run(importUserJob, new JobParameters()); System.out.println("Job Status: " + execution.getStatus()); } } ``` - **监控**:访问 H2 控制台(`http://localhost:8080/h2-console`)查看数据是否写入 `users` 表。 #### 5. **最佳实践与优化** - **性能优化**:调整 `chunk` 大小(例如,基于数据量选择 100-1000 条),避免内存溢出[^3]。 - **错误处理**:在 Step 中添加 `skipLimit` 和 `retryLimit` 来处理异常。 - **调度**:结合 Spring Scheduler 或 Quartz 实现定时批处理(参考引用[^2])。 - **扩展性**:对于大型任务,使用分区(Partitioning)将数据分片处理。 通过这个教程,您可以快速上手 Spring Batch 开发。批处理任务的核心在于分解任务为可管理的步骤,Spring Batch 的模块化设计让这变得简单高效[^1][^3]。如果您有更多数据源(如数据库或 API),只需替换 ItemReader/ItemWriter 实现即可。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大道之简

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值