You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Batch端到端测试中数据库未初始化问题排查

问题:Spring Batch测试中BATCH_JOB_INSTANCE表未初始化,执行Step失败

在Spring Batch项目中构建端到端测试时,执行单个Step提示BATCH_JOB_INSTANCE表不存在,但contextLoads测试和自定义ItemReader的单独测试正常运行,已配置spring.batch.initialize-schema=always,却无法自动初始化Spring Batch元数据表。

相关代码与配置

应用类

@EnableTransactionManagement
@SpringBootApplication
public class DadosSegurancaApplication {

    public static void main(String[] args) {
        SpringApplication.run(DadosSegurancaApplication.class, args);
    }

}

Job配置类

@Configuration
public class JobConfigs extends DefaultBatchConfiguration {

...
    @Bean
    public Step loadSubtractedObjectToDbStep(JobRepository jobRepository, PlatformTransactionManager transactionManager, ExcelReader excelReader, RepositoryItemWriter<SubtractedObject> subtractedObjectItemWriter) {
        return new StepBuilder("loadSubtractedObjectToDbStep", jobRepository)
                .<SubtractedObject, SubtractedObject>chunk(ROWS_PER_CHUNK, transactionManager)
                .reader(excelReader)
                .writer(subtractedObjectItemWriter)
                .build();
    }

    @Bean
    public Job subtractedObjectsJob(JobRepository jobRepository, Step loadSubtractedObjectToDbStep) {
        return new JobBuilder("subtractedObjectsJob", jobRepository)
               .start(loadSubtractedObjectToDbStep)
               .build();
    }
...

}

应用配置(application.properties)

spring.datasource.url=jdbc:h2:file:./local.db
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.batch.initialize-schema=always

测试类

@SpringJUnitConfig
@SpringBatchTest
@SpringBootTest(classes = { DadosSegurancaApplication.class })
public class SubtractedObjectExcelStepTest {

    @Autowired
    private ExcelReader reader;

    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;

    @Autowired
    private Job subtractedObjectsJob;

    @Test // FAILS WITH ERROR BELLOW
    public void executeSingleStep() {
        jobLauncherTestUtils.setJob(subtractedObjectsJob);
        jobLauncherTestUtils.launchStep("loadSubtractedObjectToDbStep");
    }

    @Test // WORKS
    public void testAllReads() throws Exception {
        assertSingle(reader.read(), "READ");
        assertSingle(reader.read(), "READ2");
        assertSingle(reader.read(), "READ3");
        Assertions.assertNull(reader.read());
    }

}

错误堆栈信息

org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [SELECT JOB_INSTANCE_ID, JOB_NAME
FROM BATCH_JOB_INSTANCE
WHERE JOB_NAME = ?
 and JOB_KEY = ?]

    at org.springframework.jdbc.support.SQLExceptionSubclassTranslator.doTranslate(SQLExceptionSubclassTranslator.java:103)
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:107)
    at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1548)
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:677)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:723)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:754)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:767)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:825)
    at org.springframework.batch.core.repository.dao.JdbcJobInstanceDao.getJobInstance(JdbcJobInstanceDao.java:183)
    at org.springframework.batch.core.repository.support.SimpleJobRepository.getLastJobExecution(SimpleJobRepository.java:299)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:569)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:355)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:379)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
    at jdk.proxy2/jdk.proxy2.$Proxy102.getLastJobExecution(Unknown Source)
    at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:111)
    at org.springframework.batch.core.launch.support.TaskExecutorJobLauncher.run(TaskExecutorJobLauncher.java:59)
    at org.springframework.batch.test.StepRunner.launchJob(StepRunner.java:175)
    at org.springframework.batch.test.StepRunner.launchStep(StepRunner.java:165)
    at org.springframework.batch.test.JobLauncherTestUtils.launchStep(JobLauncherTestUtils.java:241)
    at org.springframework.batch.test.JobLauncherTestUtils.launchStep(JobLauncherTestUtils.java:188)
    at unit.SubtractedObjectExcelStepTest.executeSingleStep(SubtractedObjectExcelStepTest.java:56)
    at java.base/java.lang.reflect.Method.invoke(Method.java:569)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "BATCH_JOB_INSTANCE" not found; SQL statement:
SELECT JOB_INSTANCE_ID, JOB_NAME
FROM BATCH_JOB_INSTANCE
WHERE JOB_NAME = ?
 and JOB_KEY = ? [42102-224]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:514)
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:489)
    at org.h2.message.DbException.get(DbException.java:223)
    at org.h2.message.DbException.get(DbException.java:199)
    at org.h2.command.Parser.getTableOrViewNotFoundDbException(Parser.java:8064)
    at org.h2.command.Parser.getTableOrViewNotFoundDbException(Parser.java:8035)
    at org.h2.command.Parser.readTableOrView(Parser.java:8024)
    at org.h2.command.Parser.readTablePrimary(Parser.java:1788)
    at org.h2.command.Parser.readTableReference(Parser.java:2268)
    at org.h2.command.Parser.parseSelectFromPart(Parser.java:2718)
    at org.h2.command.Parser.parseSelect(Parser.java:2824)
    at org.h2.command.Parser.parseQueryPrimary(Parser.java:2708)
    at org.h2.command.Parser.parseQueryTerm(Parser.java:2564)
    at org.h2.command.Parser.parseQueryExpressionBody(Parser.java:2543)
    at org.h2.command.Parser.parseQueryExpressionBodyAndEndOfQuery(Parser.java:2536)
    at org.h2.command.Parser.parseQueryExpression(Parser.java:2529)
    at org.h2.command.Parser.parseQuery(Parser.java:2498)
    at org.h2.command.Parser.parsePrepared(Parser.java:627)
    at org.h2.command.Parser.parse(Parser.java:592)
    at org.h2.command.Parser.parse(Parser.java:564)
    at org.h2.command.Parser.prepareCommand(Parser.java:483)
    at org.h2.engine.SessionLocal.prepareLocal(SessionLocal.java:639)
    at org.h2.engine.SessionLocal.prepareCommand(SessionLocal.java:559)
    at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1166)
    at org.h2.jdbc.JdbcPreparedStatement.<init>(JdbcPreparedStatement.java:93)
    at org.h2.jdbc.JdbcConnection.prepareStatement(JdbcConnection.java:316)
    at com.zaxxer.hikari.pool.ProxyConnection.prepareStatement(ProxyConnection.java:328)
    at com.zaxxer.hikari.pool.HikariProxyConnection.prepareStatement(HikariProxyConnection.java)
    at org.springframework.jdbc.core.JdbcTemplate$SimplePreparedStatementCreator.createPreparedStatement(JdbcTemplate.java:1711)
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:656)
    ... 28 more

原因分析

核心原因是自定义继承了DefaultBatchConfiguration的配置类,会禁用Spring Boot对Spring Batch的自动配置,包括元数据表的自动初始化逻辑。

Spring Boot的BatchAutoConfiguration会在没有自定义BatchConfiguration实现类时自动生效,负责处理Spring Batch元数据表的初始化、核心组件(如JobRepository、JobLauncher)的自动配置。但当你定义了继承DefaultBatchConfiguration的配置类后,Spring Boot会认为你要完全自定义Spring Batch配置,自动配置逻辑会退出,此时spring.batch.initialize-schema=always的配置不会触发元数据表初始化。

解决方案

方案1:移除DefaultBatchConfiguration继承,使用Spring Boot自动配置

如果不需要自定义JobRepository、JobLauncher等核心组件,直接去掉配置类的extends DefaultBatchConfiguration,让Spring Boot自动配置接管:

@Configuration
public class JobConfigs { // 移除继承关系
    // 原有的Step、Job Bean定义保持不变
    @Bean
    public Step loadSubtractedObjectToDbStep(JobRepository jobRepository, PlatformTransactionManager transactionManager, ExcelReader excelReader, RepositoryItemWriter<SubtractedObject> subtractedObjectItemWriter) {
        return new StepBuilder("loadSubtractedObjectToDbStep", jobRepository)
                .<SubtractedObject, SubtractedObject>chunk(ROWS_PER_CHUNK, transactionManager)
                .reader(excelReader)
                .writer(subtractedObjectItemWriter)
                .build();
    }

    @Bean
    public Job subtractedObjectsJob(JobRepository jobRepository, Step loadSubtractedObjectToDbStep) {
        return new JobBuilder("subtractedObjectsJob", jobRepository)
               .start(loadSubtractedObjectToDbStep)
               .build();
    }
}

修改后,spring.batch.initialize-schema=always会自动触发元数据表初始化,测试执行时即可找到对应的表。

方案2:若必须继承DefaultBatchConfiguration,手动添加元数据表初始化逻辑

如果需要自定义Spring Batch核心组件(比如调整JobRepository的事务隔离级别),可以在配置类中手动引入BatchDataSourceScriptDatabaseInitializer来触发元数据表初始化:

@Configuration
public class JobConfigs extends DefaultBatchConfiguration {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private BatchProperties batchProperties;

    // 手动配置元数据表初始化器
    @Bean
    public BatchDataSourceScriptDatabaseInitializer batchDataSourceScriptDatabaseInitializer() {
        return new BatchDataSourceScriptDatabaseInitializer(dataSource, batchProperties.getSchema());
    }

    // 原有的Step、Job Bean定义保持不变
    // ...
}

此方案需保留application.properties中的spring.batch.initialize-schema=always配置,该属性会控制初始化逻辑是否执行。

额外验证

确保测试类的@SpringBootTest加载的主应用类能扫描到所有配置类,如果测试类与配置类不在同一包下,需确认@SpringBootApplication的扫描范围覆盖了配置类(可通过@ComponentScan指定扫描路径)。


内容的提问来源于stack exchange,提问作者Allan Vital

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 15:34:50