Browse Source
This commit moves the existing JDBC-based Spring Batch infrastructure to a new 'spring-boot-batch-jdbc' module, while the existing module only offers in-memory (aka resourceless) support. The commit also updates the reference guide to provide some more information about what's available and how to use it. Closes gh-46307pull/47353/head
39 changed files with 1900 additions and 1089 deletions
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
[[io.spring-batch]] |
||||
= Spring Batch |
||||
|
||||
Spring Boot offers several conveniences for working with {url-spring-batch-site}[Spring Batch], including running a Job on startup. |
||||
|
||||
If Spring Batch is available on your classpath, it is initialized through the javadoc:org.springframework.batch.core.configuration.annotation.EnableBatchProcessing[format=annotation] annotation. |
||||
|
||||
When building a batch application, the following stores can be auto-configured: |
||||
|
||||
* In-memory |
||||
* JDBC |
||||
|
||||
Each store has specific additional settings. |
||||
For instance, it is possible to customize the tables prefix for the JDBC store, as shown in the following example: |
||||
|
||||
[configprops,yaml] |
||||
---- |
||||
spring: |
||||
batch: |
||||
jdbc: |
||||
table-prefix: "CUSTOM_" |
||||
---- |
||||
|
||||
You can take control over Spring Batch's configuration using javadoc:org.springframework.batch.core.configuration.annotation.EnableBatchProcessing[format=annotation]. |
||||
This will cause the auto-configuration to back off. |
||||
Spring Batch can then be configured using the `@Enable*JobRepository` annotation's attributes rather than the previously described configuration properties. |
||||
|
||||
|
||||
|
||||
[[io.spring-batch.running-jobs-on-startup]] |
||||
== Running Spring Batch Jobs on Startup |
||||
|
||||
When Spring Boot auto-configures Spring Batch, and if a single javadoc:org.springframework.batch.core.Job[] bean is found in the application context, it is executed on startup (see javadoc:org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner[] for details). |
||||
If multiple javadoc:org.springframework.batch.core.Job[] beans are found, the job that should be executed must be specified using configprop:spring.batch.job.name[]. |
||||
|
||||
You can disable running a javadoc:org.springframework.batch.core.Job[] found in the application context, as shown in the following example: |
||||
|
||||
[configprops,yaml] |
||||
---- |
||||
spring: |
||||
batch: |
||||
job: |
||||
enabled: false |
||||
---- |
||||
|
||||
|
||||
See javadoc:org.springframework.boot.batch.autoconfigure.BatchAutoConfiguration[] and javadoc:org.springframework.boot.batch.jdbc.autoconfigure.BatchJdbcAutoConfiguration[] for more details. |
||||
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
/* |
||||
* Copyright 2012-present the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the License); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
plugins { |
||||
id "java-library" |
||||
id "org.springframework.boot.auto-configuration" |
||||
id "org.springframework.boot.configuration-properties" |
||||
id "org.springframework.boot.deployed" |
||||
id "org.springframework.boot.optional-dependencies" |
||||
} |
||||
|
||||
description = "Spring Boot Batch JDBC" |
||||
|
||||
dependencies { |
||||
api(project(":module:spring-boot-batch")) |
||||
api(project(":module:spring-boot-jdbc")) |
||||
|
||||
implementation(project(":module:spring-boot-tx")) |
||||
|
||||
optional(project(":core:spring-boot-autoconfigure")) |
||||
optional(project(":module:spring-boot-hibernate")) |
||||
optional(project(":module:spring-boot-micrometer-observation")) |
||||
|
||||
testImplementation(project(":core:spring-boot-test")) |
||||
testImplementation(project(":module:spring-boot-flyway")) |
||||
testImplementation(project(":module:spring-boot-liquibase")) |
||||
testImplementation(project(":test-support:spring-boot-test-support")) |
||||
testImplementation(testFixtures(project(":core:spring-boot-autoconfigure"))) |
||||
testImplementation("io.micrometer:micrometer-observation-test") |
||||
|
||||
testRuntimeOnly("ch.qos.logback:logback-classic") |
||||
testRuntimeOnly("com.fasterxml.jackson.core:jackson-databind") |
||||
testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") |
||||
testRuntimeOnly("com.h2database:h2") |
||||
testRuntimeOnly("com.zaxxer:HikariCP") |
||||
} |
||||
@ -0,0 +1,192 @@
@@ -0,0 +1,192 @@
|
||||
/* |
||||
* Copyright 2012-present the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.batch.jdbc.autoconfigure; |
||||
|
||||
import java.util.List; |
||||
|
||||
import javax.sql.DataSource; |
||||
|
||||
import org.jspecify.annotations.Nullable; |
||||
|
||||
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; |
||||
import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration; |
||||
import org.springframework.batch.core.configuration.support.JdbcDefaultBatchConfiguration; |
||||
import org.springframework.batch.core.converter.JobParametersConverter; |
||||
import org.springframework.batch.core.launch.JobOperator; |
||||
import org.springframework.batch.core.repository.ExecutionContextSerializer; |
||||
import org.springframework.beans.factory.ObjectProvider; |
||||
import org.springframework.boot.autoconfigure.AutoConfiguration; |
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
||||
import org.springframework.boot.batch.autoconfigure.BatchAutoConfiguration; |
||||
import org.springframework.boot.batch.autoconfigure.BatchConversionServiceCustomizer; |
||||
import org.springframework.boot.batch.autoconfigure.BatchJobLauncherAutoConfiguration; |
||||
import org.springframework.boot.batch.autoconfigure.BatchTaskExecutor; |
||||
import org.springframework.boot.batch.autoconfigure.BatchTransactionManager; |
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties; |
||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; |
||||
import org.springframework.boot.sql.autoconfigure.init.OnDatabaseInitializationCondition; |
||||
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer; |
||||
import org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Conditional; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.context.annotation.Import; |
||||
import org.springframework.core.convert.support.ConfigurableConversionService; |
||||
import org.springframework.core.task.TaskExecutor; |
||||
import org.springframework.jdbc.datasource.init.DatabasePopulator; |
||||
import org.springframework.transaction.PlatformTransactionManager; |
||||
import org.springframework.transaction.annotation.Isolation; |
||||
|
||||
/** |
||||
* {@link EnableAutoConfiguration Auto-configuration} for Spring Batch using a JDBC store. |
||||
* |
||||
* @author Dave Syer |
||||
* @author Eddú Meléndez |
||||
* @author Kazuki Shimizu |
||||
* @author Mahmoud Ben Hassine |
||||
* @author Lars Uffmann |
||||
* @author Lasse Wulff |
||||
* @author Yanming Zhou |
||||
* @since 4.0.0 |
||||
*/ |
||||
@AutoConfiguration(before = { BatchAutoConfiguration.class, BatchJobLauncherAutoConfiguration.class }, |
||||
after = { DataSourceAutoConfiguration.class, TransactionAutoConfiguration.class }, |
||||
afterName = "org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration") |
||||
@ConditionalOnClass({ JobOperator.class, DataSource.class, DatabasePopulator.class }) |
||||
@ConditionalOnBean({ DataSource.class, PlatformTransactionManager.class }) |
||||
@ConditionalOnMissingBean(value = DefaultBatchConfiguration.class, annotation = EnableBatchProcessing.class) |
||||
@EnableConfigurationProperties(BatchJdbcProperties.class) |
||||
@Import(DatabaseInitializationDependencyConfigurer.class) |
||||
public final class BatchJdbcAutoConfiguration { |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class SpringBootBatchJdbcConfiguration extends JdbcDefaultBatchConfiguration { |
||||
|
||||
private final DataSource dataSource; |
||||
|
||||
private final PlatformTransactionManager transactionManager; |
||||
|
||||
private final @Nullable TaskExecutor taskExecutor; |
||||
|
||||
private final BatchJdbcProperties properties; |
||||
|
||||
private final List<BatchConversionServiceCustomizer> batchConversionServiceCustomizers; |
||||
|
||||
private final @Nullable ExecutionContextSerializer executionContextSerializer; |
||||
|
||||
private final @Nullable JobParametersConverter jobParametersConverter; |
||||
|
||||
SpringBootBatchJdbcConfiguration(DataSource dataSource, |
||||
@BatchDataSource ObjectProvider<DataSource> batchDataSource, |
||||
PlatformTransactionManager transactionManager, |
||||
@BatchTransactionManager ObjectProvider<PlatformTransactionManager> batchTransactionManager, |
||||
@BatchTaskExecutor ObjectProvider<TaskExecutor> batchTaskExecutor, BatchJdbcProperties properties, |
||||
ObjectProvider<BatchConversionServiceCustomizer> batchConversionServiceCustomizers, |
||||
ObjectProvider<ExecutionContextSerializer> executionContextSerializer, |
||||
ObjectProvider<JobParametersConverter> jobParametersConverter) { |
||||
this.dataSource = batchDataSource.getIfAvailable(() -> dataSource); |
||||
this.transactionManager = batchTransactionManager.getIfAvailable(() -> transactionManager); |
||||
this.taskExecutor = batchTaskExecutor.getIfAvailable(); |
||||
this.properties = properties; |
||||
this.batchConversionServiceCustomizers = batchConversionServiceCustomizers.orderedStream().toList(); |
||||
this.executionContextSerializer = executionContextSerializer.getIfAvailable(); |
||||
this.jobParametersConverter = jobParametersConverter.getIfAvailable(); |
||||
} |
||||
|
||||
@Override |
||||
protected DataSource getDataSource() { |
||||
return this.dataSource; |
||||
} |
||||
|
||||
@Override |
||||
protected PlatformTransactionManager getTransactionManager() { |
||||
return this.transactionManager; |
||||
} |
||||
|
||||
@Override |
||||
protected String getTablePrefix() { |
||||
String tablePrefix = this.properties.getTablePrefix(); |
||||
return (tablePrefix != null) ? tablePrefix : super.getTablePrefix(); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean getValidateTransactionState() { |
||||
return this.properties.isValidateTransactionState(); |
||||
} |
||||
|
||||
@Override |
||||
protected Isolation getIsolationLevelForCreate() { |
||||
Isolation isolation = this.properties.getIsolationLevelForCreate(); |
||||
return (isolation != null) ? isolation : super.getIsolationLevelForCreate(); |
||||
} |
||||
|
||||
@Override |
||||
protected ConfigurableConversionService getConversionService() { |
||||
ConfigurableConversionService conversionService = super.getConversionService(); |
||||
for (BatchConversionServiceCustomizer customizer : this.batchConversionServiceCustomizers) { |
||||
customizer.customize(conversionService); |
||||
} |
||||
return conversionService; |
||||
} |
||||
|
||||
@Override |
||||
protected ExecutionContextSerializer getExecutionContextSerializer() { |
||||
return (this.executionContextSerializer != null) ? this.executionContextSerializer |
||||
: super.getExecutionContextSerializer(); |
||||
} |
||||
|
||||
@Override |
||||
@Deprecated(since = "4.0.0", forRemoval = true) |
||||
@SuppressWarnings("removal") |
||||
protected JobParametersConverter getJobParametersConverter() { |
||||
return (this.jobParametersConverter != null) ? this.jobParametersConverter |
||||
: super.getJobParametersConverter(); |
||||
} |
||||
|
||||
@Override |
||||
protected TaskExecutor getTaskExecutor() { |
||||
return (this.taskExecutor != null) ? this.taskExecutor : super.getTaskExecutor(); |
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
@Conditional(OnBatchDatasourceInitializationCondition.class) |
||||
static class DataSourceInitializerConfiguration { |
||||
|
||||
@Bean |
||||
@ConditionalOnMissingBean |
||||
BatchDataSourceScriptDatabaseInitializer batchDataSourceInitializer(DataSource dataSource, |
||||
@BatchDataSource ObjectProvider<DataSource> batchDataSource, BatchJdbcProperties properties) { |
||||
return new BatchDataSourceScriptDatabaseInitializer(batchDataSource.getIfAvailable(() -> dataSource), |
||||
properties); |
||||
} |
||||
|
||||
} |
||||
|
||||
static class OnBatchDatasourceInitializationCondition extends OnDatabaseInitializationCondition { |
||||
|
||||
OnBatchDatasourceInitializationCondition() { |
||||
super("Batch", "spring.batch.jdbc.initialize-schema"); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,116 @@
@@ -0,0 +1,116 @@
|
||||
/* |
||||
* Copyright 2012-present the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.batch.jdbc.autoconfigure; |
||||
|
||||
import org.jspecify.annotations.Nullable; |
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
import org.springframework.boot.sql.init.DatabaseInitializationMode; |
||||
import org.springframework.transaction.annotation.Isolation; |
||||
|
||||
/** |
||||
* Configuration properties for Spring Batch using a JDBC store. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 4.0.0 |
||||
*/ |
||||
@ConfigurationProperties("spring.batch.jdbc") |
||||
public class BatchJdbcProperties { |
||||
|
||||
private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/springframework/" |
||||
+ "batch/core/schema-@@platform@@.sql"; |
||||
|
||||
/** |
||||
* Whether to validate the transaction state. |
||||
*/ |
||||
private boolean validateTransactionState = true; |
||||
|
||||
/** |
||||
* Transaction isolation level to use when creating job meta-data for new jobs. |
||||
*/ |
||||
private @Nullable Isolation isolationLevelForCreate; |
||||
|
||||
/** |
||||
* Path to the SQL file to use to initialize the database schema. |
||||
*/ |
||||
private String schema = DEFAULT_SCHEMA_LOCATION; |
||||
|
||||
/** |
||||
* Platform to use in initialization scripts if the @@platform@@ placeholder is used. |
||||
* Auto-detected by default. |
||||
*/ |
||||
private @Nullable String platform; |
||||
|
||||
/** |
||||
* Table prefix for all the batch meta-data tables. |
||||
*/ |
||||
private @Nullable String tablePrefix; |
||||
|
||||
/** |
||||
* Database schema initialization mode. |
||||
*/ |
||||
private DatabaseInitializationMode initializeSchema = DatabaseInitializationMode.EMBEDDED; |
||||
|
||||
public boolean isValidateTransactionState() { |
||||
return this.validateTransactionState; |
||||
} |
||||
|
||||
public void setValidateTransactionState(boolean validateTransactionState) { |
||||
this.validateTransactionState = validateTransactionState; |
||||
} |
||||
|
||||
public @Nullable Isolation getIsolationLevelForCreate() { |
||||
return this.isolationLevelForCreate; |
||||
} |
||||
|
||||
public void setIsolationLevelForCreate(@Nullable Isolation isolationLevelForCreate) { |
||||
this.isolationLevelForCreate = isolationLevelForCreate; |
||||
} |
||||
|
||||
public String getSchema() { |
||||
return this.schema; |
||||
} |
||||
|
||||
public void setSchema(String schema) { |
||||
this.schema = schema; |
||||
} |
||||
|
||||
public @Nullable String getPlatform() { |
||||
return this.platform; |
||||
} |
||||
|
||||
public void setPlatform(@Nullable String platform) { |
||||
this.platform = platform; |
||||
} |
||||
|
||||
public @Nullable String getTablePrefix() { |
||||
return this.tablePrefix; |
||||
} |
||||
|
||||
public void setTablePrefix(@Nullable String tablePrefix) { |
||||
this.tablePrefix = tablePrefix; |
||||
} |
||||
|
||||
public DatabaseInitializationMode getInitializeSchema() { |
||||
return this.initializeSchema; |
||||
} |
||||
|
||||
public void setInitializeSchema(DatabaseInitializationMode initializeSchema) { |
||||
this.initializeSchema = initializeSchema; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
/* |
||||
* Copyright 2012-present the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
/** |
||||
* Auto-configuration for Spring Batch JDBC. |
||||
*/ |
||||
@NullMarked |
||||
package org.springframework.boot.batch.jdbc.autoconfigure; |
||||
|
||||
import org.jspecify.annotations.NullMarked; |
||||
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
{ |
||||
"properties": [ |
||||
{ |
||||
"name": "spring.batch.initialize-schema", |
||||
"type": "org.springframework.boot.sql.init.DatabaseInitializationMode", |
||||
"deprecation": { |
||||
"replacement": "spring.batch.jdbc.initialize-schema", |
||||
"level": "error" |
||||
} |
||||
}, |
||||
{ |
||||
"name": "spring.batch.initializer.enabled", |
||||
"type": "java.lang.Boolean", |
||||
"description": "Create the required batch tables on startup if necessary. Enabled automatically\n if no custom table prefix is set or if a custom schema is configured.", |
||||
"deprecation": { |
||||
"replacement": "spring.batch.jdbc.initialize-schema", |
||||
"level": "error" |
||||
} |
||||
}, |
||||
{ |
||||
"name": "spring.batch.schema", |
||||
"type": "java.lang.String", |
||||
"deprecation": { |
||||
"replacement": "spring.batch.jdbc.schema", |
||||
"level": "error" |
||||
} |
||||
}, |
||||
{ |
||||
"name": "spring.batch.table-prefix", |
||||
"type": "java.lang.String", |
||||
"deprecation": { |
||||
"replacement": "spring.batch.jdbc.table-prefix", |
||||
"level": "error" |
||||
} |
||||
} |
||||
] |
||||
} |
||||
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
# Depends on Database Initialization Detectors |
||||
org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\ |
||||
org.springframework.boot.batch.autoconfigure.JobRepositoryDependsOnDatabaseInitializationDetector |
||||
org.springframework.boot.batch.jdbc.autoconfigure.JobRepositoryDependsOnDatabaseInitializationDetector |
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
org.springframework.boot.batch.jdbc.autoconfigure.BatchJdbcAutoConfiguration |
||||
@ -0,0 +1,822 @@
@@ -0,0 +1,822 @@
|
||||
/* |
||||
* Copyright 2012-present the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.batch.jdbc.autoconfigure; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
|
||||
import javax.sql.DataSource; |
||||
|
||||
import jakarta.persistence.EntityManagerFactory; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.mockito.InOrder; |
||||
import org.mockito.Mockito; |
||||
|
||||
import org.springframework.batch.core.BatchStatus; |
||||
import org.springframework.batch.core.configuration.JobRegistry; |
||||
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; |
||||
import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration; |
||||
import org.springframework.batch.core.converter.DefaultJobParametersConverter; |
||||
import org.springframework.batch.core.converter.JobParametersConverter; |
||||
import org.springframework.batch.core.converter.JsonJobParametersConverter; |
||||
import org.springframework.batch.core.job.AbstractJob; |
||||
import org.springframework.batch.core.job.Job; |
||||
import org.springframework.batch.core.job.JobExecution; |
||||
import org.springframework.batch.core.job.parameters.JobParameters; |
||||
import org.springframework.batch.core.job.parameters.JobParametersBuilder; |
||||
import org.springframework.batch.core.launch.JobOperator; |
||||
import org.springframework.batch.core.repository.ExecutionContextSerializer; |
||||
import org.springframework.batch.core.repository.JobRepository; |
||||
import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer; |
||||
import org.springframework.batch.core.repository.dao.Jackson2ExecutionContextStringSerializer; |
||||
import org.springframework.batch.core.step.Step; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; |
||||
import org.springframework.boot.CommandLineRunner; |
||||
import org.springframework.boot.DefaultApplicationArguments; |
||||
import org.springframework.boot.autoconfigure.AutoConfigurations; |
||||
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; |
||||
import org.springframework.boot.batch.autoconfigure.BatchConversionServiceCustomizer; |
||||
import org.springframework.boot.batch.autoconfigure.BatchJobLauncherAutoConfiguration; |
||||
import org.springframework.boot.batch.autoconfigure.BatchTaskExecutor; |
||||
import org.springframework.boot.batch.autoconfigure.BatchTransactionManager; |
||||
import org.springframework.boot.batch.autoconfigure.JobLauncherApplicationRunner; |
||||
import org.springframework.boot.batch.jdbc.autoconfigure.BatchJdbcAutoConfiguration.SpringBootBatchJdbcConfiguration; |
||||
import org.springframework.boot.batch.jdbc.autoconfigure.domain.City; |
||||
import org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration; |
||||
import org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration; |
||||
import org.springframework.boot.jdbc.DataSourceBuilder; |
||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; |
||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration; |
||||
import org.springframework.boot.jdbc.autoconfigure.EmbeddedDataSourceConfiguration; |
||||
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer; |
||||
import org.springframework.boot.liquibase.autoconfigure.LiquibaseAutoConfiguration; |
||||
import org.springframework.boot.sql.init.DatabaseInitializationMode; |
||||
import org.springframework.boot.sql.init.DatabaseInitializationSettings; |
||||
import org.springframework.boot.test.context.FilteredClassLoader; |
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner; |
||||
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources; |
||||
import org.springframework.boot.testsupport.classpath.resources.WithResource; |
||||
import org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration; |
||||
import org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizationAutoConfiguration; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.context.annotation.Primary; |
||||
import org.springframework.core.annotation.Order; |
||||
import org.springframework.core.convert.support.ConfigurableConversionService; |
||||
import org.springframework.core.task.AsyncTaskExecutor; |
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor; |
||||
import org.springframework.core.task.SyncTaskExecutor; |
||||
import org.springframework.core.task.TaskExecutor; |
||||
import org.springframework.jdbc.BadSqlGrammarException; |
||||
import org.springframework.jdbc.core.JdbcTemplate; |
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager; |
||||
import org.springframework.jdbc.datasource.init.DatabasePopulator; |
||||
import org.springframework.orm.jpa.JpaTransactionManager; |
||||
import org.springframework.test.util.AopTestUtils; |
||||
import org.springframework.transaction.PlatformTransactionManager; |
||||
import org.springframework.transaction.annotation.Isolation; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link BatchJdbcAutoConfiguration}. |
||||
* |
||||
* @author Dave Syer |
||||
* @author Stephane Nicoll |
||||
* @author Vedran Pavic |
||||
* @author Kazuki Shimizu |
||||
* @author Mahmoud Ben Hassine |
||||
* @author Lars Uffmann |
||||
* @author Lasse Wulff |
||||
* @author Yanming Zhou |
||||
*/ |
||||
class BatchJdbcAutoConfigurationTests { |
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() |
||||
.withConfiguration(AutoConfigurations.of(BatchJobLauncherAutoConfiguration.class, |
||||
BatchJdbcAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class, |
||||
TransactionAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class)); |
||||
|
||||
@Test |
||||
void testDefaultContext() { |
||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> { |
||||
assertThat(context).hasSingleBean(JobRepository.class); |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
assertThat(context.getBean(BatchJdbcProperties.class).getInitializeSchema()) |
||||
.isEqualTo(DatabaseInitializationMode.EMBEDDED); |
||||
assertThat(new JdbcTemplate(context.getBean(DataSource.class)) |
||||
.queryForList("select * from BATCH_JOB_EXECUTION")).isEmpty(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void autoconfigurationBacksOffEntirelyIfSpringJdbcAbsent() { |
||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) |
||||
.withClassLoader(new FilteredClassLoader(DatabasePopulator.class)) |
||||
.run((context) -> { |
||||
assertThat(context).doesNotHaveBean(JobLauncherApplicationRunner.class); |
||||
assertThat(context).doesNotHaveBean(BatchDataSourceScriptDatabaseInitializer.class); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void autoConfigurationBacksOffWhenUserEnablesBatchProcessing() { |
||||
this.contextRunner |
||||
.withUserConfiguration(EnableBatchProcessingConfiguration.class, EmbeddedDataSourceConfiguration.class) |
||||
.withClassLoader(new FilteredClassLoader(DatabasePopulator.class)) |
||||
.run((context) -> assertThat(context).doesNotHaveBean(BatchJdbcAutoConfiguration.class)); |
||||
} |
||||
|
||||
@Test |
||||
void autoConfigurationBacksOffWhenUserProvidesBatchConfiguration() { |
||||
this.contextRunner.withUserConfiguration(CustomBatchConfiguration.class, EmbeddedDataSourceConfiguration.class) |
||||
.withClassLoader(new FilteredClassLoader(DatabasePopulator.class)) |
||||
.run((context) -> assertThat(context).doesNotHaveBean(BatchJdbcAutoConfiguration.class)); |
||||
} |
||||
|
||||
@Test |
||||
void testDefinesAndLaunchesJob() { |
||||
this.contextRunner.withUserConfiguration(JobConfiguration.class, EmbeddedDataSourceConfiguration.class) |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
context.getBean(JobLauncherApplicationRunner.class) |
||||
.run(new DefaultApplicationArguments("jobParam=test")); |
||||
JobParameters jobParameters = new JobParametersBuilder().addString("jobParam", "test") |
||||
.toJobParameters(); |
||||
assertThat(context.getBean(JobRepository.class).getLastJobExecution("job", jobParameters)).isNotNull(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testDefinesAndLaunchesJobIgnoreOptionArguments() { |
||||
this.contextRunner.withUserConfiguration(JobConfiguration.class, EmbeddedDataSourceConfiguration.class) |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
context.getBean(JobLauncherApplicationRunner.class) |
||||
.run(new DefaultApplicationArguments("--spring.property=value", "jobParam=test")); |
||||
JobParameters jobParameters = new JobParametersBuilder().addString("jobParam", "test") |
||||
.toJobParameters(); |
||||
assertThat(context.getBean(JobRepository.class).getLastJobExecution("job", jobParameters)).isNotNull(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testRegisteredAndLocalJob() { |
||||
this.contextRunner |
||||
.withUserConfiguration(NamedJobConfigurationWithRegisteredAndLocalJob.class, |
||||
EmbeddedDataSourceConfiguration.class) |
||||
.withPropertyValues("spring.batch.job.name:discreteRegisteredJob") |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
context.getBean(JobLauncherApplicationRunner.class).run(); |
||||
assertThat(context.getBean(JobRepository.class) |
||||
.getLastJobExecution("discreteRegisteredJob", new JobParameters()) |
||||
.getStatus()).isEqualTo(BatchStatus.COMPLETED); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testDefinesAndLaunchesLocalJob() { |
||||
this.contextRunner |
||||
.withUserConfiguration(NamedJobConfigurationWithLocalJob.class, EmbeddedDataSourceConfiguration.class) |
||||
.withPropertyValues("spring.batch.job.name:discreteLocalJob") |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
context.getBean(JobLauncherApplicationRunner.class).run(); |
||||
assertThat(context.getBean(JobRepository.class) |
||||
.getLastJobExecution("discreteLocalJob", new JobParameters())).isNotNull(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testMultipleJobsAndNoJobName() { |
||||
this.contextRunner.withUserConfiguration(MultipleJobConfiguration.class, EmbeddedDataSourceConfiguration.class) |
||||
.run((context) -> { |
||||
assertThat(context).hasFailed(); |
||||
assertThat(context.getStartupFailure().getCause().getMessage()) |
||||
.contains("Job name must be specified in case of multiple jobs"); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testMultipleJobsAndJobName() { |
||||
this.contextRunner.withUserConfiguration(MultipleJobConfiguration.class, EmbeddedDataSourceConfiguration.class) |
||||
.withPropertyValues("spring.batch.job.name:discreteLocalJob") |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
context.getBean(JobLauncherApplicationRunner.class).run(); |
||||
assertThat(context.getBean(JobRepository.class) |
||||
.getLastJobExecution("discreteLocalJob", new JobParameters())).isNotNull(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testDisableLaunchesJob() { |
||||
this.contextRunner.withUserConfiguration(JobConfiguration.class, EmbeddedDataSourceConfiguration.class) |
||||
.withPropertyValues("spring.batch.job.enabled:false") |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
assertThat(context).doesNotHaveBean(CommandLineRunner.class); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testDisableSchemaLoader() { |
||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) |
||||
.withPropertyValues("spring.datasource.generate-unique-name=true", |
||||
"spring.batch.jdbc.initialize-schema:never") |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
assertThat(context.getBean(BatchJdbcProperties.class).getInitializeSchema()) |
||||
.isEqualTo(DatabaseInitializationMode.NEVER); |
||||
assertThat(context).doesNotHaveBean(BatchDataSourceScriptDatabaseInitializer.class); |
||||
assertThatExceptionOfType(BadSqlGrammarException.class) |
||||
.isThrownBy(() -> new JdbcTemplate(context.getBean(DataSource.class)) |
||||
.queryForList("select * from BATCH_JOB_EXECUTION")); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testUsingJpa() { |
||||
this.contextRunner |
||||
.withUserConfiguration(TestJpaConfiguration.class, EmbeddedDataSourceConfiguration.class, |
||||
HibernateJpaAutoConfiguration.class) |
||||
.run((context) -> { |
||||
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class); |
||||
// It's a lazy proxy, but it does render its target if you ask for
|
||||
// toString():
|
||||
assertThat(transactionManager.toString()).contains("JpaTransactionManager"); |
||||
assertThat(context).hasSingleBean(EntityManagerFactory.class); |
||||
// Ensure the JobRepository can be used (no problem with isolation
|
||||
// level)
|
||||
assertThat(context.getBean(JobRepository.class).getLastJobExecution("job", new JobParameters())) |
||||
.isNull(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
@WithPackageResources("custom-schema.sql") |
||||
void testRenamePrefix() { |
||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) |
||||
.withPropertyValues("spring.datasource.generate-unique-name=true", |
||||
"spring.batch.jdbc.schema:classpath:custom-schema.sql", "spring.batch.jdbc.table-prefix:PREFIX_") |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
assertThat(context.getBean(BatchJdbcProperties.class).getInitializeSchema()) |
||||
.isEqualTo(DatabaseInitializationMode.EMBEDDED); |
||||
assertThat(new JdbcTemplate(context.getBean(DataSource.class)) |
||||
.queryForList("select * from PREFIX_JOB_EXECUTION")).isEmpty(); |
||||
JobRepository jobRepository = context.getBean(JobRepository.class); |
||||
assertThat(jobRepository.findRunningJobExecutions("test")).isEmpty(); |
||||
assertThat(jobRepository.getLastJobExecution("test", new JobParameters())).isNull(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testCustomizeJpaTransactionManagerUsingProperties() { |
||||
this.contextRunner |
||||
.withUserConfiguration(TestJpaConfiguration.class, EmbeddedDataSourceConfiguration.class, |
||||
HibernateJpaAutoConfiguration.class) |
||||
.withPropertyValues("spring.transaction.default-timeout:30", |
||||
"spring.transaction.rollback-on-commit-failure:true") |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(BatchJobLauncherAutoConfiguration.class); |
||||
JpaTransactionManager transactionManager = JpaTransactionManager.class |
||||
.cast(context.getBean(SpringBootBatchJdbcConfiguration.class).getTransactionManager()); |
||||
assertThat(transactionManager.getDefaultTimeout()).isEqualTo(30); |
||||
assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testCustomizeDataSourceTransactionManagerUsingProperties() { |
||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) |
||||
.withPropertyValues("spring.transaction.default-timeout:30", |
||||
"spring.transaction.rollback-on-commit-failure:true") |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(BatchJdbcAutoConfiguration.class); |
||||
DataSourceTransactionManager transactionManager = DataSourceTransactionManager.class |
||||
.cast(context.getBean(SpringBootBatchJdbcConfiguration.class).getTransactionManager()); |
||||
assertThat(transactionManager.getDefaultTimeout()).isEqualTo(30); |
||||
assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testBatchDataSource() { |
||||
this.contextRunner.withUserConfiguration(BatchDataSourceConfiguration.class).run((context) -> { |
||||
assertThat(context).hasSingleBean(BatchJdbcAutoConfiguration.class) |
||||
.hasSingleBean(BatchDataSourceScriptDatabaseInitializer.class) |
||||
.hasBean("batchDataSource"); |
||||
DataSource batchDataSource = context.getBean("batchDataSource", DataSource.class); |
||||
assertThat(context.getBean(SpringBootBatchJdbcConfiguration.class).getDataSource()) |
||||
.isEqualTo(batchDataSource); |
||||
assertThat(context.getBean(BatchDataSourceScriptDatabaseInitializer.class)) |
||||
.hasFieldOrPropertyWithValue("dataSource", batchDataSource); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testBatchTransactionManager() { |
||||
this.contextRunner.withUserConfiguration(BatchTransactionManagerConfiguration.class).run((context) -> { |
||||
assertThat(context).hasSingleBean(BatchJdbcAutoConfiguration.class); |
||||
PlatformTransactionManager batchTransactionManager = context.getBean("batchTransactionManager", |
||||
PlatformTransactionManager.class); |
||||
assertThat(context.getBean(SpringBootBatchJdbcConfiguration.class).getTransactionManager()) |
||||
.isEqualTo(batchTransactionManager); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testBatchTaskExecutor() { |
||||
this.contextRunner |
||||
.withUserConfiguration(BatchTaskExecutorConfiguration.class, EmbeddedDataSourceConfiguration.class) |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(BatchJdbcAutoConfiguration.class).hasBean("batchTaskExecutor"); |
||||
TaskExecutor batchTaskExecutor = context.getBean("batchTaskExecutor", TaskExecutor.class); |
||||
assertThat(batchTaskExecutor).isInstanceOf(AsyncTaskExecutor.class); |
||||
assertThat(context.getBean(SpringBootBatchJdbcConfiguration.class).getTaskExecutor()) |
||||
.isEqualTo(batchTaskExecutor); |
||||
JobOperator jobOperator = AopTestUtils.getTargetObject(context.getBean(JobOperator.class)); |
||||
assertThat(jobOperator).hasFieldOrPropertyWithValue("taskExecutor", batchTaskExecutor); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void jobRepositoryBeansDependOnBatchDataSourceInitializer() { |
||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> { |
||||
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); |
||||
String[] jobRepositoryNames = beanFactory.getBeanNamesForType(JobRepository.class); |
||||
assertThat(jobRepositoryNames).isNotEmpty(); |
||||
for (String jobRepositoryName : jobRepositoryNames) { |
||||
assertThat(beanFactory.getBeanDefinition(jobRepositoryName).getDependsOn()) |
||||
.contains("batchDataSourceInitializer"); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void jobRepositoryBeansDependOnFlyway() { |
||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, FlywayAutoConfiguration.class) |
||||
.withPropertyValues("spring.batch.jdbc.initialize-schema=never") |
||||
.run((context) -> { |
||||
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); |
||||
String[] jobRepositoryNames = beanFactory.getBeanNamesForType(JobRepository.class); |
||||
assertThat(jobRepositoryNames).isNotEmpty(); |
||||
for (String jobRepositoryName : jobRepositoryNames) { |
||||
assertThat(beanFactory.getBeanDefinition(jobRepositoryName).getDependsOn()).contains("flyway", |
||||
"flywayInitializer"); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
@WithResource(name = "db/changelog/db.changelog-master.yaml", content = "databaseChangeLog:") |
||||
void jobRepositoryBeansDependOnLiquibase() { |
||||
this.contextRunner |
||||
.withUserConfiguration(EmbeddedDataSourceConfiguration.class, LiquibaseAutoConfiguration.class) |
||||
.withPropertyValues("spring.batch.jdbc.initialize-schema=never") |
||||
.run((context) -> { |
||||
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); |
||||
String[] jobRepositoryNames = beanFactory.getBeanNamesForType(JobRepository.class); |
||||
assertThat(jobRepositoryNames).isNotEmpty(); |
||||
for (String jobRepositoryName : jobRepositoryNames) { |
||||
assertThat(beanFactory.getBeanDefinition(jobRepositoryName).getDependsOn()).contains("liquibase"); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void whenTheUserDefinesTheirOwnBatchDatabaseInitializerThenTheAutoConfiguredInitializerBacksOff() { |
||||
this.contextRunner.withUserConfiguration(CustomBatchDatabaseInitializerConfiguration.class) |
||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, |
||||
DataSourceTransactionManagerAutoConfiguration.class)) |
||||
.run((context) -> assertThat(context).hasSingleBean(BatchDataSourceScriptDatabaseInitializer.class) |
||||
.doesNotHaveBean("batchDataSourceScriptDatabaseInitializer") |
||||
.hasBean("customInitializer")); |
||||
} |
||||
|
||||
@Test |
||||
void whenTheUserDefinesTheirOwnDatabaseInitializerThenTheAutoConfiguredBatchInitializerRemains() { |
||||
this.contextRunner.withUserConfiguration(CustomDatabaseInitializerConfiguration.class) |
||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, |
||||
DataSourceTransactionManagerAutoConfiguration.class)) |
||||
.run((context) -> assertThat(context).hasSingleBean(BatchDataSourceScriptDatabaseInitializer.class) |
||||
.hasBean("customInitializer")); |
||||
} |
||||
|
||||
@Test |
||||
void conversionServiceCustomizersAreCalled() { |
||||
this.contextRunner |
||||
.withUserConfiguration(EmbeddedDataSourceConfiguration.class, |
||||
ConversionServiceCustomizersConfiguration.class) |
||||
.run((context) -> { |
||||
BatchConversionServiceCustomizer customizer = context.getBean("batchConversionServiceCustomizer", |
||||
BatchConversionServiceCustomizer.class); |
||||
BatchConversionServiceCustomizer anotherCustomizer = context |
||||
.getBean("anotherBatchConversionServiceCustomizer", BatchConversionServiceCustomizer.class); |
||||
InOrder inOrder = Mockito.inOrder(customizer, anotherCustomizer); |
||||
ConfigurableConversionService configurableConversionService = context |
||||
.getBean(SpringBootBatchJdbcConfiguration.class) |
||||
.getConversionService(); |
||||
inOrder.verify(customizer).customize(configurableConversionService); |
||||
inOrder.verify(anotherCustomizer).customize(configurableConversionService); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void whenTheUserDefinesAJobNameAsJobInstanceValidates() { |
||||
JobLauncherApplicationRunner runner = createInstance("another"); |
||||
runner.setJobs(Collections.singletonList(mockJob("test"))); |
||||
runner.setJobName("test"); |
||||
runner.afterPropertiesSet(); |
||||
} |
||||
|
||||
@Test |
||||
void whenTheUserDefinesAJobNameAsRegisteredJobValidates() { |
||||
JobLauncherApplicationRunner runner = createInstance("test"); |
||||
runner.setJobName("test"); |
||||
runner.afterPropertiesSet(); |
||||
} |
||||
|
||||
@Test |
||||
void whenTheUserDefinesAJobNameThatDoesNotExistWithJobInstancesFailsFast() { |
||||
JobLauncherApplicationRunner runner = createInstance(); |
||||
runner.setJobs(Arrays.asList(mockJob("one"), mockJob("two"))); |
||||
runner.setJobName("three"); |
||||
assertThatIllegalStateException().isThrownBy(runner::afterPropertiesSet) |
||||
.withMessage("No job found with name 'three'"); |
||||
} |
||||
|
||||
@Test |
||||
void whenTheUserDefinesAJobNameThatDoesNotExistWithRegisteredJobFailsFast() { |
||||
JobLauncherApplicationRunner runner = createInstance("one", "two"); |
||||
runner.setJobName("three"); |
||||
assertThatIllegalStateException().isThrownBy(runner::afterPropertiesSet) |
||||
.withMessage("No job found with name 'three'"); |
||||
} |
||||
|
||||
@Test |
||||
void customExecutionContextSerializerIsUsed() { |
||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) |
||||
.withBean(ExecutionContextSerializer.class, Jackson2ExecutionContextStringSerializer::new) |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(Jackson2ExecutionContextStringSerializer.class); |
||||
assertThat(context.getBean(SpringBootBatchJdbcConfiguration.class).getExecutionContextSerializer()) |
||||
.isInstanceOf(Jackson2ExecutionContextStringSerializer.class); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void defaultExecutionContextSerializerIsUsed() { |
||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> { |
||||
assertThat(context).doesNotHaveBean(ExecutionContextSerializer.class); |
||||
assertThat(context.getBean(SpringBootBatchJdbcConfiguration.class).getExecutionContextSerializer()) |
||||
.isInstanceOf(DefaultExecutionContextSerializer.class); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void customJdbcPropertiesIsUsed() { |
||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) |
||||
.withPropertyValues("spring.batch.jdbc.validate-transaction-state:false", |
||||
"spring.batch.jdbc.isolation-level-for-create:READ_COMMITTED") |
||||
.run((context) -> { |
||||
SpringBootBatchJdbcConfiguration configuration = context |
||||
.getBean(SpringBootBatchJdbcConfiguration.class); |
||||
assertThat(configuration.getValidateTransactionState()).isEqualTo(false); |
||||
assertThat(configuration.getIsolationLevelForCreate()).isEqualTo(Isolation.READ_COMMITTED); |
||||
}); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
@Deprecated(since = "4.0.0", forRemoval = true) |
||||
@SuppressWarnings("removal") |
||||
void customJobParametersConverterIsUsed() { |
||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) |
||||
.withBean(JobParametersConverter.class, JsonJobParametersConverter::new) |
||||
.withPropertyValues("spring.datasource.generate-unique-name=true") |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(JsonJobParametersConverter.class); |
||||
assertThat(context.getBean(SpringBootBatchJdbcConfiguration.class).getJobParametersConverter()) |
||||
.isInstanceOf(JsonJobParametersConverter.class); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
@Deprecated(since = "4.0.0", forRemoval = true) |
||||
@SuppressWarnings("removal") |
||||
void defaultJobParametersConverterIsUsed() { |
||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> { |
||||
assertThat(context).doesNotHaveBean(JobParametersConverter.class); |
||||
assertThat(context.getBean(SpringBootBatchJdbcConfiguration.class).getJobParametersConverter()) |
||||
.isInstanceOf(DefaultJobParametersConverter.class); |
||||
}); |
||||
} |
||||
|
||||
private JobLauncherApplicationRunner createInstance(String... registeredJobNames) { |
||||
JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(mock(JobOperator.class)); |
||||
JobRegistry jobRegistry = mock(JobRegistry.class); |
||||
given(jobRegistry.getJobNames()).willReturn(Arrays.asList(registeredJobNames)); |
||||
runner.setJobRegistry(jobRegistry); |
||||
return runner; |
||||
} |
||||
|
||||
private Job mockJob(String name) { |
||||
Job job = mock(Job.class); |
||||
given(job.getName()).willReturn(name); |
||||
return job; |
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class BatchDataSourceConfiguration { |
||||
|
||||
@Bean |
||||
DataSource normalDataSource() { |
||||
return DataSourceBuilder.create().url("jdbc:h2:mem:normal").username("sa").build(); |
||||
} |
||||
|
||||
@BatchDataSource |
||||
@Bean(defaultCandidate = false) |
||||
DataSource batchDataSource() { |
||||
return DataSourceBuilder.create().url("jdbc:h2:mem:batchdatasource").username("sa").build(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class BatchTransactionManagerConfiguration { |
||||
|
||||
@Bean |
||||
DataSource dataSource() { |
||||
return DataSourceBuilder.create().url("jdbc:h2:mem:database").username("sa").build(); |
||||
} |
||||
|
||||
@Bean |
||||
@Primary |
||||
PlatformTransactionManager normalTransactionManager() { |
||||
return mock(PlatformTransactionManager.class); |
||||
} |
||||
|
||||
@BatchTransactionManager |
||||
@Bean(defaultCandidate = false) |
||||
PlatformTransactionManager batchTransactionManager() { |
||||
return mock(PlatformTransactionManager.class); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class BatchTaskExecutorConfiguration { |
||||
|
||||
@Bean |
||||
TaskExecutor taskExecutor() { |
||||
return new SyncTaskExecutor(); |
||||
} |
||||
|
||||
@BatchTaskExecutor |
||||
@Bean(defaultCandidate = false) |
||||
TaskExecutor batchTaskExecutor() { |
||||
return new SimpleAsyncTaskExecutor(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class EmptyConfiguration { |
||||
|
||||
} |
||||
|
||||
@TestAutoConfigurationPackage(City.class) |
||||
static class TestJpaConfiguration { |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class EntityManagerFactoryConfiguration { |
||||
|
||||
@Bean |
||||
EntityManagerFactory entityManagerFactory() { |
||||
return mock(EntityManagerFactory.class); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class NamedJobConfigurationWithRegisteredAndLocalJob { |
||||
|
||||
@Autowired |
||||
private JobRepository jobRepository; |
||||
|
||||
@Bean |
||||
Job discreteJob() { |
||||
AbstractJob job = new AbstractJob("discreteRegisteredJob") { |
||||
|
||||
private static int count = 0; |
||||
|
||||
@Override |
||||
public Collection<String> getStepNames() { |
||||
return Collections.emptySet(); |
||||
} |
||||
|
||||
@Override |
||||
public Step getStep(String stepName) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
protected void doExecute(JobExecution execution) { |
||||
if (count == 0) { |
||||
execution.setStatus(BatchStatus.COMPLETED); |
||||
} |
||||
else { |
||||
execution.setStatus(BatchStatus.FAILED); |
||||
} |
||||
count++; |
||||
} |
||||
}; |
||||
job.setJobRepository(this.jobRepository); |
||||
return job; |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class NamedJobConfigurationWithLocalJob { |
||||
|
||||
@Autowired |
||||
private JobRepository jobRepository; |
||||
|
||||
@Bean |
||||
Job discreteJob() { |
||||
AbstractJob job = new AbstractJob("discreteLocalJob") { |
||||
|
||||
@Override |
||||
public Collection<String> getStepNames() { |
||||
return Collections.emptySet(); |
||||
} |
||||
|
||||
@Override |
||||
public Step getStep(String stepName) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
protected void doExecute(JobExecution execution) { |
||||
execution.setStatus(BatchStatus.COMPLETED); |
||||
} |
||||
}; |
||||
job.setJobRepository(this.jobRepository); |
||||
return job; |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class MultipleJobConfiguration { |
||||
|
||||
@Autowired |
||||
private JobRepository jobRepository; |
||||
|
||||
@Bean |
||||
Job discreteJob() { |
||||
AbstractJob job = new AbstractJob("discreteLocalJob") { |
||||
|
||||
@Override |
||||
public Collection<String> getStepNames() { |
||||
return Collections.emptySet(); |
||||
} |
||||
|
||||
@Override |
||||
public Step getStep(String stepName) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
protected void doExecute(JobExecution execution) { |
||||
execution.setStatus(BatchStatus.COMPLETED); |
||||
} |
||||
}; |
||||
job.setJobRepository(this.jobRepository); |
||||
return job; |
||||
} |
||||
|
||||
@Bean |
||||
Job job2() { |
||||
return new Job() { |
||||
@Override |
||||
public String getName() { |
||||
return "discreteLocalJob2"; |
||||
} |
||||
|
||||
@Override |
||||
public void execute(JobExecution execution) { |
||||
execution.setStatus(BatchStatus.COMPLETED); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class JobConfiguration { |
||||
|
||||
@Autowired |
||||
private JobRepository jobRepository; |
||||
|
||||
@Bean |
||||
Job job() { |
||||
AbstractJob job = new AbstractJob() { |
||||
|
||||
@Override |
||||
public Collection<String> getStepNames() { |
||||
return Collections.emptySet(); |
||||
} |
||||
|
||||
@Override |
||||
public Step getStep(String stepName) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
protected void doExecute(JobExecution execution) { |
||||
execution.setStatus(BatchStatus.COMPLETED); |
||||
} |
||||
}; |
||||
job.setJobRepository(this.jobRepository); |
||||
return job; |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class CustomBatchDatabaseInitializerConfiguration { |
||||
|
||||
@Bean |
||||
BatchDataSourceScriptDatabaseInitializer customInitializer(DataSource dataSource, |
||||
BatchJdbcProperties properties) { |
||||
return new BatchDataSourceScriptDatabaseInitializer(dataSource, properties); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class CustomDatabaseInitializerConfiguration { |
||||
|
||||
@Bean |
||||
DataSourceScriptDatabaseInitializer customInitializer(DataSource dataSource) { |
||||
return new DataSourceScriptDatabaseInitializer(dataSource, new DatabaseInitializationSettings()); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class CustomBatchConfiguration extends DefaultBatchConfiguration { |
||||
|
||||
} |
||||
|
||||
@EnableBatchProcessing |
||||
@Configuration(proxyBeanMethods = false) |
||||
static class EnableBatchProcessingConfiguration { |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class ConversionServiceCustomizersConfiguration { |
||||
|
||||
@Bean |
||||
@Order(1) |
||||
BatchConversionServiceCustomizer batchConversionServiceCustomizer() { |
||||
return mock(BatchConversionServiceCustomizer.class); |
||||
} |
||||
|
||||
@Bean |
||||
@Order(2) |
||||
BatchConversionServiceCustomizer anotherBatchConversionServiceCustomizer() { |
||||
return mock(BatchConversionServiceCustomizer.class); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
/* |
||||
* Copyright 2012-present the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.batch.autoconfigure; |
||||
|
||||
import org.springframework.batch.core.launch.JobOperator; |
||||
import org.springframework.boot.ExitCodeGenerator; |
||||
import org.springframework.boot.autoconfigure.AutoConfiguration; |
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* {@link EnableAutoConfiguration Auto-configuration} for Spring Batch. If a single job is |
||||
* found in the context, it will be executed on startup. |
||||
* <p> |
||||
* Disable this behavior with {@literal spring.batch.job.enabled=false}). |
||||
* <p> |
||||
* If multiple jobs are found, a job name to execute on startup can be supplied by the |
||||
* User with : {@literal spring.batch.job.name=job1}. In this case the Runner will first |
||||
* find jobs registered as Beans, then those in the existing JobRegistry. |
||||
* |
||||
* @author Dave Syer |
||||
* @author Eddú Meléndez |
||||
* @author Kazuki Shimizu |
||||
* @author Mahmoud Ben Hassine |
||||
* @author Lars Uffmann |
||||
* @author Lasse Wulff |
||||
* @author Yanming Zhou |
||||
* @since 4.0.0 |
||||
*/ |
||||
@AutoConfiguration(after = BatchAutoConfiguration.class) |
||||
@ConditionalOnClass(JobOperator.class) |
||||
@ConditionalOnBean(JobOperator.class) |
||||
@EnableConfigurationProperties(BatchProperties.class) |
||||
public final class BatchJobLauncherAutoConfiguration { |
||||
|
||||
@Bean |
||||
@ConditionalOnMissingBean |
||||
@ConditionalOnBooleanProperty(name = "spring.batch.job.enabled", matchIfMissing = true) |
||||
JobLauncherApplicationRunner jobLauncherApplicationRunner(JobOperator jobOperator, BatchProperties properties) { |
||||
JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(jobOperator); |
||||
String jobName = properties.getJob().getName(); |
||||
if (StringUtils.hasText(jobName)) { |
||||
runner.setJobName(jobName); |
||||
} |
||||
return runner; |
||||
} |
||||
|
||||
@Bean |
||||
@ConditionalOnMissingBean(ExitCodeGenerator.class) |
||||
JobExecutionExitCodeGenerator jobExecutionExitCodeGenerator() { |
||||
return new JobExecutionExitCodeGenerator(); |
||||
} |
||||
|
||||
} |
||||
@ -1,43 +1,10 @@
@@ -1,43 +1,10 @@
|
||||
{ |
||||
"properties": [ |
||||
{ |
||||
"name": "spring.batch.initialize-schema", |
||||
"type": "org.springframework.boot.sql.init.DatabaseInitializationMode", |
||||
"deprecation": { |
||||
"replacement": "spring.batch.jdbc.initialize-schema", |
||||
"level": "error" |
||||
} |
||||
}, |
||||
{ |
||||
"name": "spring.batch.initializer.enabled", |
||||
"type": "java.lang.Boolean", |
||||
"description": "Create the required batch tables on startup if necessary. Enabled automatically\n if no custom table prefix is set or if a custom schema is configured.", |
||||
"deprecation": { |
||||
"replacement": "spring.batch.jdbc.initialize-schema", |
||||
"level": "error" |
||||
} |
||||
}, |
||||
{ |
||||
"name": "spring.batch.job.enabled", |
||||
"type": "java.lang.Boolean", |
||||
"description": "Whether to execute a Spring Batch job on startup. When multiple jobs are present in the context, set spring.batch.job.name to identify the job to execute.", |
||||
"defaultValue": true |
||||
}, |
||||
{ |
||||
"name": "spring.batch.schema", |
||||
"type": "java.lang.String", |
||||
"deprecation": { |
||||
"replacement": "spring.batch.jdbc.schema", |
||||
"level": "error" |
||||
} |
||||
}, |
||||
{ |
||||
"name": "spring.batch.table-prefix", |
||||
"type": "java.lang.String", |
||||
"deprecation": { |
||||
"replacement": "spring.batch.jdbc.table-prefix", |
||||
"level": "error" |
||||
} |
||||
} |
||||
] |
||||
} |
||||
@ -1,2 +1,3 @@
@@ -1,2 +1,3 @@
|
||||
org.springframework.boot.batch.autoconfigure.BatchAutoConfiguration |
||||
org.springframework.boot.batch.autoconfigure.BatchJobLauncherAutoConfiguration |
||||
org.springframework.boot.batch.autoconfigure.observation.BatchObservationAutoConfiguration |
||||
|
||||
@ -0,0 +1,277 @@
@@ -0,0 +1,277 @@
|
||||
/* |
||||
* Copyright 2012-present the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.batch.autoconfigure; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.batch.core.BatchStatus; |
||||
import org.springframework.batch.core.job.AbstractJob; |
||||
import org.springframework.batch.core.job.Job; |
||||
import org.springframework.batch.core.job.JobExecution; |
||||
import org.springframework.batch.core.job.parameters.JobParameters; |
||||
import org.springframework.batch.core.job.parameters.JobParametersBuilder; |
||||
import org.springframework.batch.core.launch.JobOperator; |
||||
import org.springframework.batch.core.repository.JobRepository; |
||||
import org.springframework.batch.core.step.Step; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.boot.CommandLineRunner; |
||||
import org.springframework.boot.DefaultApplicationArguments; |
||||
import org.springframework.boot.autoconfigure.AutoConfigurations; |
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Tests for {@link BatchJobLauncherAutoConfiguration}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class BatchJobLauncherAutoConfigurationTests { |
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( |
||||
AutoConfigurations.of(BatchAutoConfiguration.class, BatchJobLauncherAutoConfiguration.class)); |
||||
|
||||
@Test |
||||
void testDefinesAndLaunchesJob() { |
||||
this.contextRunner.withUserConfiguration(JobConfiguration.class).run((context) -> { |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
context.getBean(JobLauncherApplicationRunner.class).run(new DefaultApplicationArguments("jobParam=test")); |
||||
JobParameters jobParameters = new JobParametersBuilder().addString("jobParam", "test").toJobParameters(); |
||||
assertThat(context.getBean(JobRepository.class).getLastJobExecution("job", jobParameters)).isNotNull(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testDefinesAndLaunchesJobIgnoreOptionArguments() { |
||||
this.contextRunner.withUserConfiguration(JobConfiguration.class).run((context) -> { |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
context.getBean(JobLauncherApplicationRunner.class) |
||||
.run(new DefaultApplicationArguments("--spring.property=value", "jobParam=test")); |
||||
JobParameters jobParameters = new JobParametersBuilder().addString("jobParam", "test").toJobParameters(); |
||||
assertThat(context.getBean(JobRepository.class).getLastJobExecution("job", jobParameters)).isNotNull(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testRegisteredAndLocalJob() { |
||||
this.contextRunner.withUserConfiguration(NamedJobConfigurationWithRegisteredAndLocalJob.class) |
||||
.withPropertyValues("spring.batch.job.name:discreteRegisteredJob") |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
context.getBean(JobLauncherApplicationRunner.class).run(); |
||||
assertThat(context.getBean(JobRepository.class) |
||||
.getLastJobExecution("discreteRegisteredJob", new JobParameters()) |
||||
.getStatus()).isEqualTo(BatchStatus.COMPLETED); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testDefinesAndLaunchesLocalJob() { |
||||
this.contextRunner.withUserConfiguration(NamedJobConfigurationWithLocalJob.class) |
||||
.withPropertyValues("spring.batch.job.name:discreteLocalJob") |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
context.getBean(JobLauncherApplicationRunner.class).run(); |
||||
assertThat(context.getBean(JobRepository.class) |
||||
.getLastJobExecution("discreteLocalJob", new JobParameters())).isNotNull(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testMultipleJobsAndNoJobName() { |
||||
this.contextRunner.withUserConfiguration(MultipleJobConfiguration.class).run((context) -> { |
||||
assertThat(context).hasFailed(); |
||||
assertThat(context.getStartupFailure().getCause().getMessage()) |
||||
.contains("Job name must be specified in case of multiple jobs"); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testMultipleJobsAndJobName() { |
||||
this.contextRunner.withUserConfiguration(MultipleJobConfiguration.class) |
||||
.withPropertyValues("spring.batch.job.name:discreteLocalJob") |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
context.getBean(JobLauncherApplicationRunner.class).run(); |
||||
assertThat(context.getBean(JobRepository.class) |
||||
.getLastJobExecution("discreteLocalJob", new JobParameters())).isNotNull(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void testDisableLaunchesJob() { |
||||
this.contextRunner.withUserConfiguration(JobConfiguration.class) |
||||
.withPropertyValues("spring.batch.job.enabled:false") |
||||
.run((context) -> { |
||||
assertThat(context).hasSingleBean(JobOperator.class); |
||||
assertThat(context).doesNotHaveBean(CommandLineRunner.class); |
||||
}); |
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class NamedJobConfigurationWithRegisteredAndLocalJob { |
||||
|
||||
@Autowired |
||||
private JobRepository jobRepository; |
||||
|
||||
@Bean |
||||
Job discreteJob() { |
||||
AbstractJob job = new AbstractJob("discreteRegisteredJob") { |
||||
|
||||
private static int count = 0; |
||||
|
||||
@Override |
||||
public Collection<String> getStepNames() { |
||||
return Collections.emptySet(); |
||||
} |
||||
|
||||
@Override |
||||
public Step getStep(String stepName) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
protected void doExecute(JobExecution execution) { |
||||
if (count == 0) { |
||||
execution.setStatus(BatchStatus.COMPLETED); |
||||
} |
||||
else { |
||||
execution.setStatus(BatchStatus.FAILED); |
||||
} |
||||
count++; |
||||
} |
||||
}; |
||||
job.setJobRepository(this.jobRepository); |
||||
return job; |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class NamedJobConfigurationWithLocalJob { |
||||
|
||||
@Autowired |
||||
private JobRepository jobRepository; |
||||
|
||||
@Bean |
||||
Job discreteJob() { |
||||
AbstractJob job = new AbstractJob("discreteLocalJob") { |
||||
|
||||
@Override |
||||
public Collection<String> getStepNames() { |
||||
return Collections.emptySet(); |
||||
} |
||||
|
||||
@Override |
||||
public Step getStep(String stepName) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
protected void doExecute(JobExecution execution) { |
||||
execution.setStatus(BatchStatus.COMPLETED); |
||||
} |
||||
}; |
||||
job.setJobRepository(this.jobRepository); |
||||
return job; |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class MultipleJobConfiguration { |
||||
|
||||
@Autowired |
||||
private JobRepository jobRepository; |
||||
|
||||
@Bean |
||||
Job discreteJob() { |
||||
AbstractJob job = new AbstractJob("discreteLocalJob") { |
||||
|
||||
@Override |
||||
public Collection<String> getStepNames() { |
||||
return Collections.emptySet(); |
||||
} |
||||
|
||||
@Override |
||||
public Step getStep(String stepName) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
protected void doExecute(JobExecution execution) { |
||||
execution.setStatus(BatchStatus.COMPLETED); |
||||
} |
||||
}; |
||||
job.setJobRepository(this.jobRepository); |
||||
return job; |
||||
} |
||||
|
||||
@Bean |
||||
Job job2() { |
||||
return new Job() { |
||||
@Override |
||||
public String getName() { |
||||
return "discreteLocalJob2"; |
||||
} |
||||
|
||||
@Override |
||||
public void execute(JobExecution execution) { |
||||
execution.setStatus(BatchStatus.COMPLETED); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class JobConfiguration { |
||||
|
||||
@Autowired |
||||
private JobRepository jobRepository; |
||||
|
||||
@Bean |
||||
Job job() { |
||||
AbstractJob job = new AbstractJob() { |
||||
|
||||
@Override |
||||
public Collection<String> getStepNames() { |
||||
return Collections.emptySet(); |
||||
} |
||||
|
||||
@Override |
||||
public Step getStep(String stepName) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
protected void doExecute(JobExecution execution) { |
||||
execution.setStatus(BatchStatus.COMPLETED); |
||||
} |
||||
}; |
||||
job.setJobRepository(this.jobRepository); |
||||
return job; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
/* |
||||
* Copyright 2012-present the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the License); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
plugins { |
||||
id "java" |
||||
} |
||||
|
||||
description = "Spring Boot Batch with JDBC smoke test" |
||||
|
||||
dependencies { |
||||
implementation(project(":starter:spring-boot-starter-batch-jdbc")) |
||||
|
||||
runtimeOnly("org.hsqldb:hsqldb") |
||||
|
||||
testImplementation(project(":starter:spring-boot-starter-test")) |
||||
} |
||||
@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
|
||||
/* |
||||
* Copyright 2012-present the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package smoketest.batch; |
||||
|
||||
import org.springframework.batch.core.job.Job; |
||||
import org.springframework.batch.core.job.builder.JobBuilder; |
||||
import org.springframework.batch.core.repository.JobRepository; |
||||
import org.springframework.batch.core.step.Step; |
||||
import org.springframework.batch.core.step.builder.StepBuilder; |
||||
import org.springframework.batch.core.step.tasklet.Tasklet; |
||||
import org.springframework.batch.repeat.RepeatStatus; |
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.transaction.PlatformTransactionManager; |
||||
|
||||
@SpringBootApplication |
||||
public class SampleBatchApplication { |
||||
|
||||
@Bean |
||||
Tasklet tasklet() { |
||||
return (contribution, context) -> RepeatStatus.FINISHED; |
||||
} |
||||
|
||||
@Bean |
||||
Job job(JobRepository jobRepository, Step step) { |
||||
return new JobBuilder("job", jobRepository).start(step).build(); |
||||
} |
||||
|
||||
@Bean |
||||
Step step1(JobRepository jobRepository, Tasklet tasklet, PlatformTransactionManager transactionManager) { |
||||
return new StepBuilder("step1", jobRepository).tasklet(tasklet, transactionManager).build(); |
||||
} |
||||
|
||||
public static void main(String[] args) { |
||||
// System.exit is common for Batch applications since the exit code can be used to
|
||||
// drive a workflow
|
||||
System.exit(SpringApplication.exit(SpringApplication.run(SampleBatchApplication.class, args))); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
/* |
||||
* Copyright 2012-present the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
@NullMarked |
||||
package smoketest.batch; |
||||
|
||||
import org.jspecify.annotations.NullMarked; |
||||
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
/* |
||||
* Copyright 2012-present the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package smoketest.batch; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
|
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.test.system.CapturedOutput; |
||||
import org.springframework.boot.test.system.OutputCaptureExtension; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
@ExtendWith(OutputCaptureExtension.class) |
||||
class SampleBatchApplicationTests { |
||||
|
||||
@Test |
||||
void testDefaultSettings(CapturedOutput output) { |
||||
assertThat(SpringApplication.exit(SpringApplication.run(SampleBatchApplication.class))).isZero(); |
||||
assertThat(output).contains("completed with the following parameters"); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
/* |
||||
* Copyright 2012-present the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the License); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
plugins { |
||||
id "org.springframework.boot.starter" |
||||
} |
||||
|
||||
description = "Starter for using Spring Batch with JDBC" |
||||
|
||||
dependencies { |
||||
api(project(":starter:spring-boot-starter")) |
||||
api(project(":starter:spring-boot-starter-jdbc")) |
||||
|
||||
api(project(":module:spring-boot-batch-jdbc")) |
||||
} |
||||
Loading…
Reference in new issue