Spring Batch报错JobRepository must be set的解决及多数据源配置问询
我是Spring Batch新手,正在编写无Spring Boot的POC代码,实现从MySQL导出数据到CSV,但运行时出现以下错误:
Failed to instantiate [org.springframework.batch.core.Job]: Factory method 'job' threw exception with message: java.lang.IllegalStateException: JobRepository must be set
我需要解决这个配置遗漏的问题,同时要为JobRepository配置与业务数据源不同的数据源。以下是我的代码:
主类代码
import com.techtalk.debu.batch.entity.Employee; import org.springframework.batch.core.*; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.item.database.JdbcCursorItemReader; import org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder; import org.springframework.batch.item.file.FlatFileItemWriter; import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.core.io.FileSystemResource; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; @EnableBatchProcessing(dataSourceRef = "batchDataSource", transactionManagerRef = "batchTransactionManager") public class LoadController { @Bean public DataSource batchDataSource() { return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2) .addScript("/org/springframework/batch/core/schema-h2.sql") .generateUniqueName(false).build(); } @Bean public DataSource dataSource() { DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); dataSourceBuilder.url("jdbc:mysql://localhost:3306/Test"); dataSourceBuilder.username("sa"); dataSourceBuilder.password("1234567890"); return dataSourceBuilder.build(); } @Bean public JdbcTransactionManager batchTransactionManager(DataSource batchDataSource) { return new JdbcTransactionManager(batchDataSource); } public static void main(String e []) throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext(LoadController.class); JobParameters jobParameters = new JobParametersBuilder().addLong("time", System.currentTimeMillis()).toJobParameters(); JobLauncher jobLauncher = context.getBean(JobLauncher.class); JobExecution jobExecution = jobLauncher.run(context.getBean(Job.class), jobParameters); System.out.println("Job Exit Status : " + jobExecution.getStatus()); } @Bean public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step", jobRepository) .<Employee, Employee>chunk(5, transactionManager) .reader(itemReader()) .writer(itemWriter()) .build(); } @Bean public JdbcTransactionManager transactionManager(DataSource dataSource) { return new JdbcTransactionManager(dataSource); } @Bean public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager) { return new JobBuilder("job") .start(step(jobRepository, transactionManager)) .build(); } @Bean public JdbcCursorItemReader<Employee> itemReader() { String sql = "select * from person"; return new JdbcCursorItemReaderBuilder<Employee>() .name("personItemReader") .dataSource(dataSource()) .sql(sql) .beanRowMapper(Employee.class) .build(); } @Bean public FlatFileItemWriter<Employee> itemWriter() { return new FlatFileItemWriterBuilder<Employee>() .resource(new FileSystemResource("persons.csv")) .name("personItemWriter") .delimited() .names("id", "name") .build(); } }
Maven Pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.2</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.techtalk.debu</groupId> <artifactId>Spring-Batch-CSV-to-DB-Project</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-batch-CSV-To_DB-Write-Example-For-Beginners</name> <description>Demo project for Beginners to understand Spring Boot</description> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>3.0.2</version> </dependency> <dependency> <groupId>org.springframework.batch</groupId> <artifactId>spring-batch-core</artifactId> <version>5.0.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.21</version> </dependency> <dependency> <groupId>org.springframework.batch</groupId> <artifactId>spring-batch-infrastructure</artifactId> <version>5.0.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Employee实体类
import jakarta.persistence.*; import lombok.Data; import java.util.Date; @Entity @Table(name = "user_analytics_table") @Data public class Employee { @Id private String id; private String name; private String email; private String phone_number; }
问题分析与解决方案
核心问题
创建Job时使用JobBuilder但未传入JobRepository实例,导致框架无法找到必要的作业元数据存储组件。虽然@EnableBatchProcessing已指定batch专用的数据源和事务管理器,但JobBuilder需要显式关联JobRepository才能正常初始化。
修复步骤
修正Job Bean的创建逻辑
在job方法中,将JobRepository传入JobBuilder的构造方法(与创建Step的方式一致):@Bean public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager) { return new JobBuilder("job", jobRepository) // 新增JobRepository参数 .start(step(jobRepository, transactionManager)) .build(); }规范ItemReader的数据源注入
当前itemReader()直接调用dataSource()创建新实例,会绕过Spring容器的依赖管理,改为通过方法参数注入业务数据源:@Bean public JdbcCursorItemReader<Employee> itemReader(DataSource dataSource) { String sql = "select * from person"; return new JdbcCursorItemReaderBuilder<Employee>() .name("personItemReader") .dataSource(dataSource) .sql(sql) .beanRowMapper(Employee.class) .build(); }移除Spring Boot相关依赖(可选)
若要实现纯无Spring Boot的POC,可替换Pom中的parent和依赖:<!-- 替换原Spring Boot parent --> <parent> <groupId>org.springframework.batch</groupId> <artifactId>spring-batch-parent</artifactId> <version>5.0.0</version> <relativePath /> </parent> <!-- 添加必要的Spring基础依赖,移除Spring Boot相关 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> <!-- 移除spring-boot-starter-data-jpa和spring-boot-maven-plugin -->
验证效果
修改后,Spring容器会自动将batch专用的JobRepository注入到Job和Step组件中,同时业务数据源与batch数据源相互隔离,满足需求。
内容的提问来源于stack exchange,提问作者Tamer Saleh

