首页 / 资讯中心 / 文章详情

Spring Batch项目搭建实战:从零创建Job与Step的完整教程

Spring Batch项目搭建实战:从零创建Job与Step的完整教程 ★ FEATURED ARTICLE
很多资料讲Spring Batch上来就是Job、Step、Chunk的概念图看得人云里雾里。真要自己动手从零搭建一个Spring Batch项目跑批处理任务时最先拦住你的往往不是业务逻辑写不出来而是项目还没起来就被依赖、版本、数据库初始化这些细节卡死了。这篇文章直接围绕“如何搭建一个Spring Batch项目”这条主线把创建工程、写依赖、配数据源、定义Job和Step、跑通第一个批处理任务的每个环节拆开讲。适合刚接触批处理框架、或者想快速落地一个可运行项目的开发者参考看完你能照着搭出自己的第一个Spring Batch工程。我尽量避免那种“复制粘贴就能跑但完全不知道在干什么”的教程风格每一个关键配置、每一个看似普通的注解我都会解释它到底干了什么以及不这么做会踩什么坑。毕竟批处理框架这东西入门容易真正要在生产环境里稳着跑很多细节是从文档里看不出来的。1. 先想清楚再动手项目设计与方案选型1.1 Spring Batch到底解决什么问题不少新手会有个困惑我要处理100万条数据写个for循环不就行了为什么要引入一个框架这个问题我确实被问过很多次。用for循环处理几十条静态数据确实没问题但当你面对的是几万、几十万条数据并且处理过程中可能涉及远程接口调用、数据库分批写入、失败重试、断点续跑时for循环就完全扛不住了。你没地方记录“上次跑到第几条”中途挂了只能从头再来事务边界怎么切也说不清处理失败的个别数据会拖垮整批整个过程没有任何监控指标出了问题只能靠瞎猜。Spring Batch解决的就是这些痛点。它把批处理拆成“读、处理、写”三个固定环节每个环节都有状态记录。整个任务对应Job一个Job可以拆成多个Step每个Step又按chunk切分事务边界。跑批过程中的启动时间、结束时间、状态、读了多少条、写了多少条、失败了多少条全部落在数据库里。你不需要自己设计任务执行记录表Spring Batch自带的元数据表已经把这事做完了。打个比方for循环是一个人一页页手抄文件抄错一页要全篇返工Spring Batch是一条流水线每个工位有计数器哪个工位出问题只影响那一块的记录重新启动时还能从断点处接着干。把数据批处理当成一条需要监控、可控、可恢复的流水线来做而不是一段临时脚本这是Spring Batch最核心的价值。1.2 技术栈怎么选才不给自己挖坑搭建Spring Batch项目最常见的组合是Spring Boot Spring Batch 关系型数据库。Spring Boot负责自动配置和项目骨架Spring Batch提供完整的批处理能力MySQL或PostgreSQL这类数据库用来存放Job和Step的执行元数据。这里要注意Spring Batch本身不做定时调度它只负责“任务怎么拆、怎么执行、怎么记录”不负责“几点开始跑”。定时触发是Spring Scheduling、Quartz这类调度框架的职责。实际项目里常见做法是调度框架到了时间点调用JobLauncher去启动一个Batch Job。初学者不用一上来就搞调度集成先把Job通过命令行或者简单的启动方式跑通再考虑定时触发。版本选择方面如果你用的是JDK 8建议直接选Spring Boot 2.7.x这是2.x系列的最后一个稳定版本线和spring-boot-starter-batch的兼容性最成熟。如果你的环境是JDK 17及以上那要用Spring Boot 3.x对应的Spring Batch也变成了5.x版本API有破坏性调整比如EnableBatchProcessing的行为、JobBuilderFactory这类工厂类被废弃写法上跟2.x差了不少。我自己建议纯粹为了搞懂原理先用2.7.x入门等你把Job、Step、Chunk、Reader/Processor/Writer这套模型吃透了再迁移到3.x也不难。代码结构上推荐按职责分包简单清晰例如com.example.batchdemo ├── BatchDemoApplication.java ├── config │ └── BatchConfig.java ├── entity │ └── User.java ├── reader │ └── UserItemReader.java ├── processor │ └── UserItemProcessor.java └── writer └── UserItemWriter.java不要把所有Bean和逻辑堆在一个类里批处理项目一旦任务多了一个类几百行很难维护。每个Step对应一个明确职责Reader只负责读Processor只负责处理Writer只负责写Spring Batch这套分层本来就是这么设计的我们写代码也应该保持这个节奏。2. 项目搭建实操从空白目录到可运行工程2.1 环境与版本怎么搭配先把环境清单列出来照着准备就行。我用的是一套比较保守、不太容易出幺蛾子的组合。工具版本建议说明JDK1.8Spring Boot 2.7.x最低要求Maven3.6以上项目构建和依赖管理IDE任意支持Maven的IDE日常开发足够了MySQL5.7或8.0存放业务表和Batch元数据表H2内嵌版本本地快速验证不依赖外部数据库如果你本机暂时没有MySQL我强烈建议先引入H2依赖里加上就能用Spring Batch官方对H2的兼容性很好本地调试时不用操心建库建用户。等任务逻辑跑通了再切到MySQL只需要改数据源配置。2.2 Maven依赖配置细节创建Maven项目后pom.xml是最先要处理的东西。核心依赖其实没几个但版本千万不要自己手写一堆尽量交给Spring Boot的parent统一管理。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent properties java.version1.8/java.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-batch/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build几个关键的依赖为什么要加spring-boot-starter-batch是核心它会把spring-boot-starter、spring-batch-core、spring-batch-infrastructure自动带进来所以不需要手动引Batch的细节模块。spring-boot-starter-jdbc也别漏因为后面多半要用JdbcCursorItemReader或者JdbcBatchItemWriter它们都依赖DataSource操作。只引了Batch不引JDBC相关依赖后面用到数据库读写时会缺类。mysql-connector-j是MySQL 8的驱动包坐标在老项目中常见的是mysql-connector-java那是旧坐标了建议用新的。驱动版本不需要自己指定Spring Boot parent已经管理好了。H2依赖加上之后本地联调阶段可以先用内嵌库跑绕开MySQL的环境问题。等本项目经验足够了再切换MySQL只改连接配置即可。我见过一个很典型的翻车现场一个人手动引入了spring-batch-core 4.0.x又让Spring Boot 2.3.x的parent引入了4.1.x结果类冲突一堆启动直接NoSuchMethodError。Batch依赖和Boot版本错位的问题非常隐蔽最好的办法就是像上面这样全交给starter-parent管理不给版本冲突留机会。2.3 配置文件与数据库表说明依赖写好后在resources目录下新建application.yml配置数据源和Batch相关参数。spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/batch_demo?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 batch: jdbc: initialize-schema: always table-prefix: BATCH_ job: enabled: false先说数据源URL里的几个参数每一个都是血的教训serverTimezoneAsia/Shanghai不写的话MySQL 8连接经常报server time zone时区错误这个坑出现率极高。allowPublicKeyRetrievaltrueMySQL 8启用SSL相关的公钥检索机制后不配置这个参数会报Public Key Retrieval is not allowed。如果报这个错加上就好。characterEncodingutf8避免中文乱码。再说Batch配置spring.batch.jdbc.initialize-schemaalways的意思是启动时自动执行Spring Batch官方提供的建表脚本创建那几张元数据表。如果连的库里已经有这些表可以改成never。生产环境我建议不要用always而是用脚本手动初始化避免每次启动都去执行DDL。但本地学习阶段always是最省事的。spring.batch.job.enabledfalse这个配置非常关键。Spring Boot一旦在classpath里检测到Spring Batch并且容器里有Job类型的Bean启动时默认会把所有Job自动跑一遍。你现在还没想让它跑或者想自己控制启动时机就必须显式关掉。这个配置忘了加应用一启动任务就自动执行新手的翻车率非常高。这些表包括BATCH_JOB_INSTANCE、BATCH_JOB_EXECUTION、BATCH_JOB_EXECUTION_CONTEXT、BATCH_STEP_EXECUTION、BATCH_STEP_EXECUTION_CONTEXT、BATCH_JOB_EXECUTION_PARAMS等。如果你没建过表打开数据库看到一批BATCH_开头的新表别慌那正是JobRepository落地需要的基础设施。表名作用BATCH_JOB_INSTANCE记录Job实例每个唯一Job参数组合对应一条记录BATCH_JOB_EXECUTION记录一次Job执行的所有信息包括状态、起止时间BATCH_STEP_EXECUTION记录每个Step执行的状态和统计信息BATCH_JOB_EXECUTION_CONTEXT保存Job级ExecutionContext序列化数据BATCH_STEP_EXECUTION_CONTEXT保存Step级ExecutionContext序列化数据BATCH_JOB_EXECUTION_PARAMS保存每次执行传入的JobParameters刚开始你不需要把每张表的字段都背下来但至少要明白Spring Batch把任务执行历史和参数全部持久化到数据库这就是它能断点续跑、能查历史执行情况的基础。没有这套表JobRepository就没有落点框架也就退化成一段普通的遍历逻辑了。3. 一步步写出你的第一个Job3.1 启动类与Batch核心开关入口类长这样很简单package com.example.batchdemo; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication EnableBatchProcessing public class BatchDemoApplication { public static void main(String[] args) { SpringApplication.run(BatchDemoApplication.class, args); } }重点是EnableBatchProcessing。这个注解在Spring Boot 2.x中会自动装配一批核心Bean包括JobLauncher、JobRepository、JobBuilderFactory、StepBuilderFactory等。也就是说没有它你在配置类里想注入JobBuilderFactory和StepBuilderFactory是注入不进去的启动阶段就会报找不到类型匹配的Bean错误。这里提一下Spring Boot 3.x的变化如果你以后用3.x搭建官方推荐的方式已经不再依赖EnableBatchProcessing而是直接用JobRepository、JobBuilder、StepBuilder这些类去手动构建。2.x的写法简单是因为工厂类帮你处理了很多细节3.x则把控制权交还给开发者。初学阶段用2.x没问题但心里要有这个版本差异的意识。3.2 Job与Step的定义方式Job和Step是Spring Batch里最核心的概念。Job是整个批任务一个Job包含一个或多个StepStep是Job中一个独立的执行单元它只能做两类事情要么是Tasklet要么是Chunk处理。Tasklet适合不需要读写数据的场景比如“启动前清理临时目录”“生成一个汇总标记”写起来很简单Bean public Step cleanStep() { return stepBuilderFactory.get(cleanStep) .tasklet((contribution, chunkContext) - { System.out.println(执行清理任务); return RepeatStatus.FINISHED; }) .build(); }返回RepeatStatus.FINISHED表示这个步骤执行一次就完成如果返回CONTINUABLE框架会反复执行这个Tasklet直到你显式返回FINISHED。这个机制容易让人踩坑比如有人想在Tasklet里循环处理数据结果忘了返回FINISHED日志里看到Tasklet被无限执行。搞清楚RepeatStatus的含义就好办了。Chunk方式则适合数据批处理场景每条数据经过Reader、Processor、Writer三个环节一批批提交事务。一个基本的Job配置如下package com.example.batchdemo.config; import com.example.batchdemo.entity.User; import com.example.batchdemo.processor.UserItemProcessor; import com.example.batchdemo.reader.UserItemReader; import com.example.batchdemo.writer.UserItemWriter; 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.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class BatchConfig { private final JobBuilderFactory jobBuilderFactory; private final StepBuilderFactory stepBuilderFactory; public BatchConfig(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory) { this.jobBuilderFactory jobBuilderFactory; this.stepBuilderFactory stepBuilderFactory; } Bean public Job demoJob(Step demoStep) { return jobBuilderFactory.get(demoJob) .incrementer(new RunIdIncrementer()) .start(demoStep) .build(); } Bean public Step demoStep(UserItemReader reader, UserItemProcessor processor, UserItemWriter writer) { return stepBuilderFactory.get(demoStep) .User, Userchunk(100) .reader(reader) .processor(processor) .writer(writer) .build(); } }incrementer(new RunIdIncrementer())是干什么的Spring Batch规定同一个JobInstance只能被成功执行一次如果再次用相同的JobParameters启动会直接报“A job instance already exists”。RunIdIncrementer会在每次启动时自动给JobParameters增加一个run.id自增参数相当于每次执行都携带全新参数这样同一任务就可以反复执行。在开发调试阶段没有它你会频繁撞上重复执行的报错。chunk(100)不是很多人理解的那种“攒够100条再一起处理”。它的工作机制是框架读一条处理一条再读一条处理一条当读到第100条时把这一批100条统一交给Writer一次写入。整个100条构成一个事务边界提交点就在这里。chunk size要根据数据量和事务耗时来调后面我会单独说。对于chunk泛型User, User第一个泛型是Reader产出的数据类型第二个大的是Writer接收的数据类型。要是Processor做了类型转换比如把User转成VO这里的泛型就变成User, SomeVo框架会在类型不匹配时直接报错提前暴露问题。3.3 数据读写三件套详解先看最基础的自定义ItemReader。ItemReader接口只有一个方法read()每调用一次返回一条数据返回null表示数据读完了。这个“返回null即结束”约定一定要记住很多人自己在循环里控制结束却不清楚框架靠null来关闭读取循环。package com.example.batchdemo.reader; import com.example.batchdemo.entity.User; import org.springframework.batch.item.ItemReader; import java.util.Arrays; import java.util.List; public class UserItemReader implements ItemReaderUser { private final ListUser users Arrays.asList( new User(1L, Alice, 25), new User(2L, Bob, 35), new User(3L, Cindy, 19) ); private int index 0; Override public User read() { if (index users.size()) { return users.get(index); } return null; } }实际项目中我们很少纯手写Reader更多用现成实现。读文件用FlatFileItemReader读数据库用JdbcCursorItemReader或JdbcPagingItemReader。我第一次做文件批处理时用的就是FlatFileItemReader主要配置是resource、lineMapper和encoding只要配好了encodingUTF-8中文内容一般不会出乱码。再看ItemProcessor。它负责每条数据的加工和过滤最容易被忽略的用法是返回null表示这条数据不参与后续写入也就是一种天然过滤机制。package com.example.batchdemo.processor; import com.example.batchdemo.entity.User; import org.springframework.batch.item.ItemProcessor; public class UserItemProcessor implements ItemProcessorUser, User { Override public User process(User user) { if (user.getAge() 18) { return null; } user.setAge(user.getAge() 1); return user; } }这里如果是未成年数据直接返回null这个user就不会进入Writer但框架不会因为一条数据处理异常就终止整个Job。注意区分返回null和抛出异常前者是合法过滤后者是异常中断。最后是ItemWriter。它接收的不是一条数据而是一个List这正好和chunk机制对应。你可以自己继承ItemWriter做一些批量落库操作也可以直接用JdbcBatchItemWriter做批量写入。package com.example.batchdemo.writer; import com.example.batchdemo.entity.User; import org.springframework.batch.item.ItemWriter; import java.util.List; public class UserItemWriter implements ItemWriterUser { Override public void write(List? extends User userList) { for (User user : userList) { System.out.println(写入用户: user); } } }这里有个容易被忽视的点不要把一个有状态的Reader实例同时复用给多个Step。比如你的Reader内部维护了一个index另一个Step如果也用了同一个Bean实例读取会从上次的位置继续数据会错位。正确做法是每个Step声明自己的Reader实例或者确保Reader是无状态的。4. 一个真实场景的批处理Demo跑起来4.1 Demo场景与表结构设计单纯打印到控制台没有说服力这里我设计一个更贴近实际的小Demo模拟某个会员系统的积分计算任务。业务要求很简单从user表读取用户信息年龄大于等于25岁的用户发100积分小于25岁的发50积分然后写入user_point表。这个场景覆盖了数据库读、加工计算、批量写三个环节是典型的批处理应用。建两张表。CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(64) DEFAULT NULL, age INT DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE user_point ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT DEFAULT NULL, point INT DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;插入几条测试数据方便后面验证结果INSERT INTO user (id, name, age) VALUES (1, Alice, 25), (2, Bob, 35), (3, Cindy, 19);实体类使用Lombok简化package com.example.batchdemo.entity; import lombok.Data; Data public class User { private Long id; private String name; private Integer age; private Integer point; }注意这里多了一个point字段是为了配合JdbcBatchItemWriter做属性映射写入积分。4.2 完整代码实现先定义UserPointProcessor实现积分计算逻辑package com.example.batchdemo.processor; import com.example.batchdemo.entity.User; import org.springframework.batch.item.ItemProcessor; public class UserPointProcessor implements ItemProcessorUser, User { Override public User process(User user) { user.setPoint(user.getAge() 25 ? 100 : 50); return user; } }然后在BatchConfig中把Reader、Processor、Writer都声明为Bean。这里用的是JdbcCursorItemReader做数据库读JdbcBatchItemWriter做批量写。package com.example.batchdemo.config; import com.example.batchdemo.entity.User; import com.example.batchdemo.processor.UserPointProcessor; 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.database.JdbcBatchItemWriter; import org.springframework.batch.item.database.JdbcBatchItemWriterBuilder; import org.springframework.batch.item.database.JdbcCursorItemReader; import org.springframework.batch.item.database.JdbcCursorItemReaderBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; Configuration public class BatchConfig { private final JobBuilderFactory jobBuilderFactory; private final StepBuilderFactory stepBuilderFactory; public BatchConfig(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory) { this.jobBuilderFactory jobBuilderFactory; this.stepBuilderFactory stepBuilderFactory; } Bean public JdbcCursorItemReaderUser userReader(DataSource dataSource) { return new JdbcCursorItemReaderBuilderUser() .dataSource(dataSource) .name(userReader) .sql(SELECT id, name, age FROM user) .rowMapper((rs, rowNum) - new User( rs.getLong(id), rs.getString(name), rs.getInt(age) )) .build(); } Bean public UserPointProcessor userPointProcessor() { return new UserPointProcessor(); } Bean public JdbcBatchItemWriterUser userWriter(DataSource dataSource) { return new JdbcBatchItemWriterBuilderUser() .dataSource(dataSource) .sql(INSERT INTO user_point(user_id, point) VALUES (:id, :point)) .beanMapped() .build(); } Bean public Job userPointJob(Step userPointStep) { return jobBuilderFactory.get(userPointJob) .incrementer(new RunIdIncrementer()) .start(userPointStep) .build(); } Bean public Step userPointStep(JdbcCursorItemReaderUser reader, UserPointProcessor processor, JdbcBatchItemWriterUser writer) { return stepBuilderFactory.get(userPointStep) .User, Userchunk(10) .reader(reader) .processor(processor) .writer(writer) .build(); } }几个细节说明这里Reader的sql不能带分号结尾某些版本执行带分号的SQL会在游标读取时报语法错误虽然SQL本身在数据库工具里是对的。我建议统一养成SQL不加分号的习惯可以减少一处糊涂报错。beanMapped()会自动使用User对象的getter方法把属性值映射到SQL里的冒号占位符。也就是说SQL中的:point会对应user.getPoint():id对应user.getId()。参数名必须和实体属性名一致否则运行时会告诉你无法解析这个属性。JdbcCursorItemReader的原理是基于JDBC游标每次从ResultSet里取一行大数据量下内存开销小。阅读大量数据时它一般比分页Reader更直接不过要求数据库连接在整个Step执行期间保持打开生产环境要注意连接租用时长。写到这里如果你要手动触发这个Job可以做一个CommandLineRunner通过JobLauncher来启动。这是我最常用的调试方式因为它对启动时机的控制最明确。package com.example.batchdemo.runner; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.JobParametersBuilder; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; Component public class JobLauncherRunner implements CommandLineRunner { private final JobLauncher jobLauncher; private final Job userPointJob; public JobLauncherRunner(JobLauncher jobLauncher, Job userPointJob) { this.jobLauncher jobLauncher; this.userPointJob userPointJob; } Override public void run(String... args) throws Exception { JobParameters params new JobParametersBuilder() .addLong(time, System.currentTimeMillis()) .toJobParameters(); jobLauncher.run(userPointJob, params); } }其实这里做了“双保险”Job配置了RunIdIncrementerCommandLineRunner在启动时又传入了时间戳参数JobParameters每次执行都不一样任务重复启动就不会被JobInstance机制拦截。如果你用的是Spring Boot自动运行那job.enabled要保持true但本地调试我还是建议用Runner控制一下。4.3 启动验证与结果检查启动应用后正常情况下你会在日志里看到类似这样的输出2024-xx-xx 12:00:00.123 INFO ... Job: [SimpleJob: [nameuserPointJob]] launched with the following parameters: [{time1700000000000}] 2024-xx-xx 12:00:00.456 INFO ... Step: [userPointStep] executed in 1s 2024-xx-xx 12:00:00.789 INFO ... Job: [SimpleJob: [nameuserPointJob]] completed successfully看到completed successfully说明Job整体成功。这时查一下user_point表SELECT * FROM user_point;不出意外能看到三条记录iduser_idpoint11100221003350同时再查一下BATCH_JOB_INSTANCE表会看到userPointJob的实例记录查BATCH_STEP_EXECUTION表能看到userPointStep这次执行的READ_COUNT、WRITE_COUNT、COMMIT_COUNT。这一步很关键它让你直观地理解Spring Batch是怎么把执行元数据落到数据库里的。以后你要判断一个批任务有没有成功、处理了多少条不再靠肉眼数日志直接查这些表就可以。5. 避坑实录与常见问题速查5.1 新手最容易遇到的6个报错这段时间我用Spring Batch的反复翻车经验整理了下面这张表基本覆盖了从搭项目到跑第一个Job的高频问题。现象原因解决方案启动报BATCH_JOB_INSTANCE表不存在元数据表没有被初始化把spring.batch.jdbc.initialize-schema设为always或手动执行官方schema脚本应用启动时任务自动执行了spring.batch.job.enabled没有设为false配置spring.batch.job.enabledfalse再通过JobLauncher手动触发第二次执行报JobInstance already exists使用了相同的JobParameters再次启动配置RunIdIncrementer或者每次启动传入不同的参数如时间戳读取文件出现中文乱码FlatFileItemReader没有指定编码在Reader配置中设置encodingUTF-8MySQL连接报server time zone错误JDBC连接参数缺少时区配置URL加上serverTimezoneAsia/Shanghai启动报No qualifying bean of type JobRepositoryEnableBatchProcessing缺失或版本冲突检查启动类是否有EnableBatchProcessing以及依赖版本是否和Boot匹配每个问题单独拿出来都不复杂但架不住一起出现。我见过一个同学第一行配置就写错了表前缀日志一直报“table BATCH_JOB_INSTANCE not found”结果他连数据源都还没配花了一下午排查。遇到报错先去检查配置再看代码不要一上来就在业务逻辑里猜。5.2 实操心得与重要提醒最后分享几条实操心得这些是在文档里很难快速获得的经验。调试批处理任务时我建议第一次跑就把chunk size设得小一点比如10。这样每批写入的数据量小日志里可以清楚看到每10条一批的提交过程哪一批出了问题也能快速定位。调优参数是后面的事一开始最重要的是把整个处理链路看清楚。生产环境里我一般会根据Writer的单批耗时来定chunk比如单批写入耗时在0.5秒到2秒之间这个概念可以帮你判断chunk是调大还是调小。不是越大越好chunk太大会拉长单次事务时间数据库锁和回滚范围都变大反而容易出问题。关于并行Step很多初学者一上来就想玩拆分并行。我自己的经验是先把单Step跑通再考虑并行。Spring Batch的并行Step对资源隔离和事务配置有要求处理不好会出现“一个Step还没结束另一个Step又开始了”的错误排查起来比串行麻烦得多。第一步能稳定产出结果是后面所有优化的大前提。还有一点要对生产环境特别上心元数据表的数据会持续增长。每次执行都会往BATCH_JOB_EXECUTION、BATCH_STEP_EXECUTION等表插入记录跑得越多表越大。如果任务量大建议制定归档策略定期把旧的历史执行记录备份清理。表设计上也可以让DBA协助确认索引比如JOB_EXECUTION、STEP_EXECUTION这类频繁查询的表建好索引能明显减少查询压力。另外如果你想用Spring Batch做失败重试初学阶段先不要上skip和retry策略这两个机制的配置复杂度不低。先保证正常路径跑通再针对单条数据失败做捕获处理最后再考虑框架级的重试和跳过策略这样排查问题的范围会小很多。讲讲我个人的搭建习惯任何新项目我先用H2加一个简单Reader写一个最小Demo确认整个框架链路是通的再替换成真实数据源和业务组件。这样环境问题、版本问题、配置问题都在最小范围暴露排查起来非常快。这个习惯救了我很多次尤其是框架跨版本升级的时候先让最小任务跑起来再逐步加复杂特性比直接引入完整业务要稳得多。学会了搭建下一篇可以继续聊任务重启策略和执行监听到时候你可以把Job的每一步执行情况都掌握在手里。
阅读完成 · 觉得有帮助?
咨询建站