乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 批处理概述与 Spring Batch 核心理念

    • Spring Batch 概述
    • Job-Instance-Execution 三层生命周期
    • 三层架构
    • Chunk 处理模型
    • Tasklet 处理模型
  • 第2章 Job与作业配置

    • Job 详解
    • Job 配置
    • JobLauncher 详解
    • JobParameters 详解
    • JobRepository 详解
  • 第3章 Step与执行模型

    • Step 详解
    • Step 配置
    • ExecutionContext 详解
    • ItemStream 与状态管理
    • StepScope 与 JobScope
  • 第4章 ItemReader数据读取

    • ItemReader 详解
    • FlatFileItemReader 详解
    • JdbcItemReader 详解
    • MultiResourceItemReader 详解
  • 第5章 ItemProcessor 数据处理

    • ItemProcessor 详解
  • 第6章 ItemWriter 数据写出

    • ItemWriter 详解
    • FlatFileItemWriter 详解
    • CompositeItemWriter 详解
    • JdbcBatchItemWriter 详解
  • 第7章 Chunk 处理与事务边界

    • Chunk 处理模型详解
    • 事务边界
  • 第8章 作业参数与启动

    • 命令行与 Web 启动
  • 第9章 监听器与拦截器

    • 监听器详解
  • 第10章 重启重试与跳过策略

    • 重启与重试
    • 跳过策略
  • 第11章 作业流与条件决策

    • 作业流与条件决策
  • 第12章 分区与并行处理

    • 分区详解
    • 并行 Step 与 Split
  • 第13章 与 Spring Boot 集成实践

    • Spring Boot 集成
  • 第15章 运维与监控

    • 大作业设计模式
    • 运维与监控

Tasklet 处理模型

本章定位:理解 Step 的另一种实现方式 Tasklet——何时用、怎么用、和 Chunk 模式如何选型。

定义与作用

Tasklet 是 Step 的另一种实现方式,通过 Tasklet.execute() 方法执行单一任务逻辑。与 Chunk 模式(Reader → Processor → Writer 循环)不同,Tasklet 在一个方法调用中完成所有工作,返回 RepeatStatus.FINISHED 表示完成,返回 RepeatStatus.CONTINUABLE 表示还有工作要做。

飞翔科技架构师白歌这样区分两种 Step:

"Chunk Step 是'流水线'——数据在传送带上经过每个工位。Tasklet Step 是'一个操作'——清缓存、调存储过程、发邮件。不是所有事情都适合流水线模式。"

核心原理

Tasklet 执行流程

Tasklet vs Chunk Step 对比

维度Chunk StepTasklet Step
处理模型流水线:Reader → Processor → Writer单体操作:execute() 一次搞定
数据量任意大小,流式处理通常不处理数据,或处理少量聚合数据
事务每个 Chunk 一个事务整个 execute() 一个事务
重启从 ExecutionContext 恢复断点通常不可重启或自定义重启逻辑
典型场景ETL、数据迁移、报表生成文件清理、存储过程调用、初始化脚本、发送通知

完整示例

场景一:飞翔科技——每日作业前的临时文件清理

背景:运营部杨英的每日订单报表作业在运行前需要清理昨日生成的临时文件。架构师白歌建议用一个 Tasklet Step 作为 Job 的第一步。

@Component
public class FileCleanupTasklet implements Tasklet {

    private static final String TEMP_DIR = "C:/batch/temp/";

    @Override
    public RepeatStatus execute(StepContribution contribution,
                                 ChunkContext chunkContext) throws Exception {
        File tempDir = new File(TEMP_DIR);
        int deletedCount = 0;

        if (tempDir.exists()) {
            File[] files = tempDir.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.delete()) {
                        deletedCount++;
                    }
                }
            }
        }

        System.out.println("清理完成:删除了 " + deletedCount + " 个临时文件");
        return RepeatStatus.FINISHED;
    }
}

Job 配置:

@Bean
public Job dailyReportJob(JobRepository jobRepository,
                           Step cleanupStep,
                           Step processStep,
                           Step notifyStep) {
    return new JobBuilder("dailyReportJob", jobRepository)
            .start(cleanupStep)    // Step 1: Tasklet——清理临时文件
            .next(processStep)     // Step 2: Chunk——处理报表数据
            .next(notifyStep)      // Step 3: Tasklet——发送邮件通知
            .build();
}

@Bean
public Step cleanupStep(JobRepository jobRepository,
                         PlatformTransactionManager tx,
                         FileCleanupTasklet cleanupTasklet) {
    return new StepBuilder("cleanupStep", jobRepository)
            .tasklet(cleanupTasklet, tx)
            .build();
}

运行结果:

Job: [dailyReportJob] started
  Step [cleanupStep]: 清理完成:删除了 12 个临时文件
  Step [cleanupStep]: COMPLETED
  Step [processStep]: COMPLETED (5000 records)
  Step [notifyStep]: 已发送邮件通知到 ops@feixiang.net
  Step [notifyStep]: COMPLETED
Job: [dailyReportJob] COMPLETED

操作前后对比:

维度操作前(手动清理)操作后(Tasklet)
执行方式运维李眉每天手动跑脚本作为 Job 第一步自动执行
可靠性可能忘记清理,磁盘爆满每次都执行,确保磁盘空间
追踪无日志BATCH_STEP_EXECUTION 记录执行状态
与主流程的关系独立操作,无关联Job 的一部分,失败则整个 Job 失败

场景二:调用数据库存储过程

背景:飞翔科技财务系统有一个复杂的月度结算存储过程 sp_monthly_settlement,需要传入月份参数。财务部要求每月 1 号凌晨自动执行。

@Component
@StepScope
public class MonthlySettlementTasklet implements Tasklet {

    @Value("#{jobParameters['settlementMonth']}")
    private String settlementMonth;

    private final JdbcTemplate jdbcTemplate;

    public MonthlySettlementTasklet(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public RepeatStatus execute(StepContribution contribution,
                                 ChunkContext chunkContext) {
        System.out.println("开始月度结算:" + settlementMonth);

        jdbcTemplate.execute(
            "CALL sp_monthly_settlement('" + settlementMonth + "')");

        System.out.println("月度结算完成:" + settlementMonth);
        return RepeatStatus.FINISHED;
    }
}

注:@StepScope 确保 settlementMonth 在 Step 启动时才从 JobParameters 中延迟绑定。

运行结果:

Job: [monthlySettlementJob] with params: {settlementMonth=2026-05}
  Step [settlementStep]: 开始月度结算:2026-05
  Step [settlementStep]: 月度结算完成:2026-05
  Step [settlementStep]: COMPLETED
Job: [monthlySettlementJob] COMPLETED

场景三:CONTINUABLE——分页清理大批量过期数据

有时 Tasklet 需要处理的数据量也很大,但不需要 ItemReader/Processor/Writer 的流水线模式。此时可用 RepeatStatus.CONTINUABLE 实现分页执行:

@Component
public class ExpiredDataCleanupTasklet implements Tasklet {

    private static final int DELETE_BATCH_SIZE = 1000;
    private final JdbcTemplate jdbcTemplate;

    public ExpiredDataCleanupTasklet(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public RepeatStatus execute(StepContribution contribution,
                                 ChunkContext chunkContext) {
        int deleted = jdbcTemplate.update(
            "DELETE FROM audit_logs WHERE created_at < ? LIMIT ?",
            LocalDate.now().minusYears(1), DELETE_BATCH_SIZE);

        contribution.incrementWriteCount(deleted);
        System.out.println("本批删除:" + deleted + " 条");

        // 如果本批删除了数据,说明可能还有更多 → 继续
        // 如果本批没有删除数据 → 全部清理完成
        return deleted > 0
            ? RepeatStatus.CONTINUABLE
            : RepeatStatus.FINISHED;
    }
}

运行输出:

本批删除:1000 条
本批删除:1000 条
本批删除:1000 条
本批删除:423 条
本批删除:0 条 → Step 完成
总删除:3423 条

易错场景与避坑

反例一:用 Tasklet 做大批量数据处理

小崔最初把整个 CSV 导入逻辑写在 Tasklet 里:

// ❌ 错误:用 Tasklet 做 10 万行 CSV 导入——失去了 Chunk 的所有优势
@Component
public class BadCsvImportTasklet implements Tasklet {

    @Override
    public RepeatStatus execute(StepContribution contribution,
                                 ChunkContext chunkContext) {
        List<String> lines = Files.readAllLines(Paths.get("big_file.csv"));
        for (String line : lines) {
            // ... 处理逻辑
            jdbcTemplate.update("INSERT INTO ...", ...); // 逐条 SQL
        }
        return RepeatStatus.FINISHED;
    }
}

问题:

  1. Files.readAllLines() 一次性加载全部数据到内存 → 10 万行可能 OOM
  2. 逐条 update() 没有 JDBC batch → 性能极差
  3. 没有事务边界控制 → 全部成功或全部失败,无法控制回滚粒度
  4. 无法断点续传 → 失败后必须从头再来

正确做法:大数据量处理 → 必须用 Chunk Step。

反例二:CONTINUABLE 死循环

运维工程师李眉某次写了一个 CONTINUABLE 的 Tasklet,忘了确保退出条件:

// ❌ 危险:死循环——永远返回 CONTINUABLE
@Override
public RepeatStatus execute(...) {
    doSomething();
    return RepeatStatus.CONTINUABLE; // 永远不会 FINISHED!
}

后果:Step 永远不结束,直到 JVM 被 kill。

正确做法:确保每次 execute() 推进进度,且退出条件必然可达。

面试高频考点

Q1:Tasklet Step 和 Chunk Step 的区别和适用场景?

Tasklet 适合单一操作(文件清理、存储过程调用、发送通知),一次性执行完成。Chunk 适合数据流水线处理(ETL、报表生成),通过 Reader → Processor → Writer 循环处理大批量数据。前者是"一个动作",后者是"一条流水线"。

Q2:Tasklet 的 CONTINUABLE 机制如何工作?

当 execute() 返回 RepeatStatus.CONTINUABLE 时,框架会在当前事务提交后再次调用 execute()。开发者需要在每次调用中推进进度(如分页删除),并在无更多工作可做时返回 FINISHED。每次调用之间的事务边界由 Step 配置决定。

Q3:Tasklet 能实现断点续传吗?

可以,但需要开发者手动实现——通过 ExecutionContext 在每次 execute() 之间保存进度。框架不为 Tasklet 提供自动的状态管理,因为 Tasklet 的内部逻辑对框架是透明的。相比之下,Chunk Step 的断点续传由框架自动处理。


上一章:Chunk 处理模型下一章:Job 详解

上一页
Chunk 处理模型