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

    • 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章 运维与监控

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

JobRepository 详解

本章定位:深入理解 JobRepository——Spring Batch 状态管理的基石。它是如何记录每一次执行、如何支持断点续传、如何在内存和数据库模式间选择。

定义与作用

JobRepository 是 Spring Batch 的持久化机制,负责存储和管理 JobExecution、StepExecution、ExecutionContext 等元数据。它是实现状态管理、重启能力和历史追踪的物理基础。

飞翔科技架构师白歌在团队技术分享中强调:

"如果把 Spring Batch 比作一个物流系统,JobRepository 就是它的数据库——每一批货(Chunk)的签收记录、每一次运输(JobExecution)的状态、每一条路线(Step)的进度,都在里面。没有它,你无法知道哪些货已经送到、哪些还在路上。"

核心原理

JobRepository 的数据流

核心元数据表

表名存储内容关键字段写入时机
BATCH_JOB_INSTANCEJob 实例定义JOB_INSTANCE_ID, JOB_NAME, JOB_KEYJob 首次启动时
BATCH_JOB_EXECUTIONJob 执行记录JOB_EXECUTION_ID, STATUS, START_TIME, END_TIME启动时创建,结束时更新
BATCH_JOB_EXECUTION_PARAMS每次执行的参数JOB_EXECUTION_ID, KEY_NAME, KEY_TYPE, STRING_VAL与 JobExecution 同时创建
BATCH_STEP_EXECUTIONStep 执行记录STEP_EXECUTION_ID, STATUS, READ_COUNT, WRITE_COUNT每个 Step 开始/结束时
BATCH_STEP_EXECUTION_CONTEXTStep 执行上下文STEP_EXECUTION_ID, SHORT_CONTEXT (JSON)每个 Chunk 提交后
BATCH_JOB_EXECUTION_CONTEXTJob 执行上下文JOB_EXECUTION_ID, SHORT_CONTEXT (JSON)Job 执行期间

内存 vs 数据库模式

模式实现类适用场景优缺点
内存模式MapJobRepositoryFactoryBean测试、开发、演示快但重启后数据丢失,无法查询历史
数据库模式JDBC JobRepository生产环境持久化、支持重启、可查询历史
# 数据库模式配置(生产推荐)
spring.datasource.url=jdbc:mysql://localhost:3306/batch_meta
spring.batch.jdbc.initialize-schema=always  # 自动创建元数据表
spring.batch.jdbc.table-prefix=BATCH_       # 表名前缀

完整示例

场景一:飞翔科技——通过 JobExplorer 查询作业历史

背景:产品经理孔蓝需要一个管理界面查看所有批处理作业的执行历史。小崔用 JobExplorer(JobRepository 的只读查询接口)实现。

@RestController
@RequestMapping("/api/batch/history")
public class BatchHistoryController {

    private final JobExplorer jobExplorer;

    public BatchHistoryController(JobExplorer jobExplorer) {
        this.jobExplorer = jobExplorer;
    }

    @GetMapping("/jobs/{jobName}")
    public List<Map<String, Object>> getJobHistory(@PathVariable String jobName) {
        List<JobInstance> instances = jobExplorer.findJobInstancesByJobName(jobName, 0, 20);

        return instances.stream().map(instance -> {
            List<JobExecution> executions = jobExplorer.getJobExecutions(instance);
            return Map.of(
                "instanceId", instance.getId(),
                "executions", executions.stream().map(exec -> Map.of(
                    "executionId", exec.getId(),
                    "status", exec.getStatus().name(),
                    "startTime", exec.getStartTime(),
                    "endTime", exec.getEndTime(),
                    "exitCode", exec.getExitStatus().getExitCode()
                )).collect(Collectors.toList())
            );
        }).collect(Collectors.toList());
    }

    @GetMapping("/steps/{jobExecutionId}")
    public List<Map<String, Object>> getStepDetails(
            @PathVariable Long jobExecutionId) {
        JobExecution jobExec = jobExplorer.getJobExecution(jobExecutionId);
        if (jobExec == null) return List.of();

        return jobExec.getStepExecutions().stream().map(step -> Map.of(
            "stepName", step.getStepName(),
            "status", step.getStatus().name(),
            "readCount", step.getReadCount(),
            "writeCount", step.getWriteCount(),
            "commitCount", step.getCommitCount(),
            "skipCount", step.getSkipCount(),
            "startTime", step.getStartTime(),
            "endTime", step.getEndTime()
        )).collect(Collectors.toList());
    }
}

运行结果:

GET /api/batch/history/jobs/orderProcessingJob
→ 200 OK
[
  {
    "instanceId": 15,
    "executions": [
      {"executionId": 42, "status": "COMPLETED", "startTime": "...", "endTime": "..."},
      {"executionId": 41, "status": "FAILED", "startTime": "...", "endTime": "..."}
    ]
  }
]

GET /api/batch/history/steps/42
→ 200 OK
[
  {"stepName": "cleanupStep", "status": "COMPLETED", "readCount": 0, "writeCount": 0},
  {"stepName": "importStep", "status": "COMPLETED", "readCount": 5000, "writeCount": 4980,
   "commitCount": 100, "skipCount": 20},
  {"stepName": "reportStep", "status": "COMPLETED", "readCount": 0, "writeCount": 0}
]

场景二:自定义 JobRepository 配置

在需要自定义序列化器或表前缀时的配置:

@Configuration
@EnableBatchProcessing
public class CustomJobRepositoryConfig {

    @Bean
    public JobRepository customJobRepository(DataSource dataSource,
                                               PlatformTransactionManager tx) throws Exception {
        JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
        factory.setDataSource(dataSource);
        factory.setTransactionManager(tx);
        factory.setTablePrefix("CUSTOM_BATCH_");  // 自定义表前缀
        factory.setDatabaseType("MYSQL");
        factory.setMaxVarCharLength(2500);        // ExecutionContext 最大长度
        factory.afterPropertiesSet();
        return factory.getObject();
    }
}

易错场景与避坑

反例一:生产环境使用 H2 内存数据库

# ❌ 生产环境:H2 内存模式 → 重启后元数据全部丢失
spring.datasource.url=jdbc:h2:mem:batch

后果:失败 Job 无法重启,历史无法查询。

正确做法:生产环境使用 MySQL/PostgreSQL 等持久化数据库。

反例二:ExecutionContext 过大导致序列化失败

// ❌ 在 ExecutionContext 中存入大对象
executionContext.put("bigList", hugeListOfObjects);

问题:ExecutionContext 序列化为 JSON 存储在 SHORT_CONTEXT 列中,列有长度限制(默认 2500 字符)。超大数据可能导致截断或写入失败。

正确做法:ExecutionContext 只存简单的恢复点(行号、ID),不存完整数据对象。

面试高频考点

Q1:JobRepository 存储在内存和数据库的区别?

内存模式(MapJobRepositoryFactoryBean)适合测试环境,应用重启后元数据丢失。数据库模式持久化元数据,支持生产环境的状态查询、作业重启和历史追踪。生产环境必须使用数据库模式。

Q2:如何查看 Step 的只读、写入和跳过记录数?

通过 JobExplorer 查询 BATCH_STEP_EXECUTION 表,其中 READ_COUNT、WRITE_COUNT、SKIP_COUNT、COMMIT_COUNT 字段精确记录了每次 Step 执行的统计信息。也可以通过 Spring Cloud Data Flow 的 UI 可视化查看。

Q3:ExecutionContext 什么时候被持久化?

每个 Chunk 提交后,框架调用 ItemStream.update(executionContext),然后 JobRepository 将更新后的 ExecutionContext 持久化到 BATCH_STEP_EXECUTION_CONTEXT 表。这意味着即使在 Chunk 中间崩溃,上一个 Chunk 的进度已经被安全保存。


上一章:JobLauncher 详解下一章:Step 详解

上一页
JobParameters 详解