Spring Batch 报 NoUniqueBeanDefinitionException 是因 @EnableBatchProcessing 与 spring-boot-starter-batch 冲突导致双重 JobLauncher 注册;应删除该注解,确保仅引入 starter,自定义时加 @Primary;同时需验证 JobRepository 持久化、JobParameters 唯一性及数据库表名大小写适配。

Spring Batch 依赖没加对,JobLauncher 直接报 NoUniqueBeanDefinitionException
Spring Boot 2.5+ 默认不自动配置 JobLauncher,但如果你手动引入了 spring-boot-starter-batch,又额外加了 @EnableBatchProcessing,就会触发双重配置冲突——Spring 试图注册两个 JobLauncher 实例,而你没指定哪个是 primary。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 删掉显式的
@EnableBatchProcessing(Spring Boot 场景下它多余且有害) - 确认只依赖
spring-boot-starter-batch,别混用spring-batch-core等低层 jar - 如果要用自定义
JobLauncher,必须加@Primary注解,例如:@Bean @Primary public JobLauncher jobLauncher(JobRepository jobRepository) { ... } - 检查是否误把
DataSource配置成内存库(如 H2)却没关spring.batch.initialize-schema=always,这会导致启动时建表失败并静默降级为内存 JobRepository,后续执行直接抛JobExecutionNotRunningException
数据迁移场景下,JdbcCursorItemReader 查不出数据但也不报错
常见于源库字段类型和 Java 实体字段不匹配,比如数据库是 DECIMAL(19,4),Java 用了 Double,JDBC 驱动在 ResultSet 取值时悄悄返回 null,而 JdbcCursorItemReader 默认跳过 null 行且不记录日志。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 在 reader 的
setRowMapper里加非空校验,例如:rowMapper.setRowMapper((rs, rowNum) -> { BigDecimal amt = rs.getBigDecimal("amount"); if (amt == null) throw new IllegalStateException("amount is null at row " + rowNum); return new Record(amt); }); - 避免用
String接数字字段;优先用BigDecimal或Long,再按需转 - 确认 SQL 中没写死
WHERE status = 'PROCESSED'这类条件,而实际数据状态是'processed'(大小写/空格/编码差异) - 开启 JDBC 日志:
logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG,看真实执行的 SQL 和参数绑定
迁移任务跑一半挂了,重启后重复处理或跳过已处理数据
根本原因是 JobRepository 没持久化,或者 JobParameters 每次都传一样的值(比如只用时间戳但没包含毫秒),导致 Spring Batch 认为这是“同一任务”,直接跳过或报 JobInstanceAlreadyCompleteException。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 确保
DataSource指向真实数据库,并执行了 Spring Batch 初始化脚本(schema-h2.sql或对应数据库版本) - 构造
JobParameters时必须含唯一标识,推荐组合:new JobParametersBuilder() .addString("run.id", UUID.randomUUID().toString()) .addLong("time", System.currentTimeMillis()) .toJobParameters() - 不要依赖
JobParametersBuilder.addDate()—— 它精度只到秒,高并发下极易重复 - 若要支持断点续跑,reader 必须实现可重启(stateful),比如
JdbcPagingItemReader比JdbcCursorItemReader更稳妥,因它靠分页键推进,不依赖游标位置
从 MySQL 迁到 PostgreSQL,JobRepository 表名大小写崩了
PostgreSQL 默认把未加引号的表名转小写,而 Spring Batch DDL 脚本里写的是大写表名(如 BATCH_JOB_INSTANCE),导致初始化后实际建出 batch_job_instance,但代码里仍按大写查,报 Table not found。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 用官方提供的
schema-postgresql.sql,别用 H2 或 MySQL 版本改名凑合 - 在
application.yml中显式指定表前缀,避免大小写歧义:spring: batch: jdbc: table-prefix: BATCH_ - 如果已有数据,别手动改表名,而是用
SET search_path TO public;并确保 schema 名一致 - 验证方式:连上数据库执行
\dt(psql)或SELECT table_name FROM information_schema.tables WHERE table_schema='public';,确认表名全小写且存在
JobRepository 是否真在用你配的 DataSource,而不是悄悄 fallback 到内存模式。










