Create spring-boot-batch module
This commit is contained in:
committed by
Phillip Webb
parent
8d5c834df9
commit
06173670b3
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.List;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
||||
import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration;
|
||||
import org.springframework.batch.core.converter.JobParametersConverter;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.core.repository.ExecutionContextSerializer;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
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.autoconfigure.sql.init.OnDatabaseInitializationCondition;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
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;
|
||||
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 = TransactionAutoConfiguration.class,
|
||||
afterName = "org.springframework.boot.jpa.autoconfigure.hibernate.HibernateJpaAutoConfiguration")
|
||||
@ConditionalOnClass({ JobLauncher.class, DataSource.class, DatabasePopulator.class })
|
||||
@ConditionalOnBean({ DataSource.class, PlatformTransactionManager.class })
|
||||
@ConditionalOnMissingBean(value = DefaultBatchConfiguration.class, annotation = EnableBatchProcessing.class)
|
||||
@EnableConfigurationProperties(BatchProperties.class)
|
||||
@Import(DatabaseInitializationDependencyConfigurer.class)
|
||||
public class BatchAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnBooleanProperty(name = "spring.batch.job.enabled", matchIfMissing = true)
|
||||
public JobLauncherApplicationRunner jobLauncherApplicationRunner(JobLauncher jobLauncher, JobExplorer jobExplorer,
|
||||
JobRepository jobRepository, BatchProperties properties) {
|
||||
JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(jobLauncher, jobExplorer, jobRepository);
|
||||
String jobName = properties.getJob().getName();
|
||||
if (StringUtils.hasText(jobName)) {
|
||||
runner.setJobName(jobName);
|
||||
}
|
||||
return runner;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ExitCodeGenerator.class)
|
||||
public JobExecutionExitCodeGenerator jobExecutionExitCodeGenerator() {
|
||||
return new JobExecutionExitCodeGenerator();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class SpringBootBatchConfiguration extends DefaultBatchConfiguration {
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
private final PlatformTransactionManager transactionManager;
|
||||
|
||||
private final TaskExecutor taskExecutor;
|
||||
|
||||
private final BatchProperties properties;
|
||||
|
||||
private final List<BatchConversionServiceCustomizer> batchConversionServiceCustomizers;
|
||||
|
||||
private final ExecutionContextSerializer executionContextSerializer;
|
||||
|
||||
private final JobParametersConverter jobParametersConverter;
|
||||
|
||||
SpringBootBatchConfiguration(DataSource dataSource, @BatchDataSource ObjectProvider<DataSource> batchDataSource,
|
||||
PlatformTransactionManager transactionManager,
|
||||
@BatchTransactionManager ObjectProvider<PlatformTransactionManager> batchTransactionManager,
|
||||
@BatchTaskExecutor ObjectProvider<TaskExecutor> batchTaskExecutor, BatchProperties 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.getJdbc().getTablePrefix();
|
||||
return (tablePrefix != null) ? tablePrefix : super.getTablePrefix();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean getValidateTransactionState() {
|
||||
return this.properties.getJdbc().isValidateTransactionState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Isolation getIsolationLevelForCreate() {
|
||||
Isolation isolation = this.properties.getJdbc().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
|
||||
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, BatchProperties properties) {
|
||||
return new BatchDataSourceScriptDatabaseInitializer(batchDataSource.getIfAvailable(() -> dataSource),
|
||||
properties.getJdbc());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class OnBatchDatasourceInitializationCondition extends OnDatabaseInitializationCondition {
|
||||
|
||||
OnBatchDatasourceInitializationCondition() {
|
||||
super("Batch", "spring.batch.jdbc.initialize-schema");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.configuration.support.DefaultBatchConfiguration;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
|
||||
/**
|
||||
* Callback interface that can be implemented by beans wishing to customize the
|
||||
* {@link ConfigurableConversionService} that is
|
||||
* {@link DefaultBatchConfiguration#getConversionService provided by
|
||||
* DefaultBatchConfiguration} while retaining its default auto-configuration.
|
||||
*
|
||||
* @author Claudio Nave
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BatchConversionServiceCustomizer {
|
||||
|
||||
/**
|
||||
* Customize the {@link ConfigurableConversionService}.
|
||||
* @param configurableConversionService the ConfigurableConversionService to customize
|
||||
*/
|
||||
void customize(ConfigurableConversionService configurableConversionService);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
/**
|
||||
* Qualifier annotation for a DataSource to be injected into Batch auto-configuration. Can
|
||||
* be used on a secondary data source, if there is another one marked as
|
||||
* {@link Primary @Primary}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Qualifier
|
||||
public @interface BatchDataSource {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.List;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
|
||||
import org.springframework.boot.jdbc.init.PlatformPlaceholderDatabaseDriverResolver;
|
||||
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link DataSourceScriptDatabaseInitializer} for the Spring Batch database. May be
|
||||
* registered as a bean to override auto-configuration.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Vedran Pavic
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class BatchDataSourceScriptDatabaseInitializer extends DataSourceScriptDatabaseInitializer {
|
||||
|
||||
/**
|
||||
* Create a new {@link BatchDataSourceScriptDatabaseInitializer} instance.
|
||||
* @param dataSource the Spring Batch data source
|
||||
* @param properties the Spring Batch JDBC properties
|
||||
* @see #getSettings
|
||||
*/
|
||||
public BatchDataSourceScriptDatabaseInitializer(DataSource dataSource, BatchProperties.Jdbc properties) {
|
||||
this(dataSource, getSettings(dataSource, properties));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link BatchDataSourceScriptDatabaseInitializer} instance.
|
||||
* @param dataSource the Spring Batch data source
|
||||
* @param settings the database initialization settings
|
||||
* @see #getSettings
|
||||
*/
|
||||
public BatchDataSourceScriptDatabaseInitializer(DataSource dataSource, DatabaseInitializationSettings settings) {
|
||||
super(dataSource, settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts {@link BatchProperties.Jdbc Spring Batch JDBC properties} to
|
||||
* {@link DatabaseInitializationSettings} replacing any {@literal @@platform@@}
|
||||
* placeholders.
|
||||
* @param dataSource the Spring Batch data source
|
||||
* @param properties batch JDBC properties
|
||||
* @return a new {@link DatabaseInitializationSettings} instance
|
||||
* @see #BatchDataSourceScriptDatabaseInitializer(DataSource,
|
||||
* DatabaseInitializationSettings)
|
||||
*/
|
||||
public static DatabaseInitializationSettings getSettings(DataSource dataSource, BatchProperties.Jdbc properties) {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setSchemaLocations(resolveSchemaLocations(dataSource, properties));
|
||||
settings.setMode(properties.getInitializeSchema());
|
||||
settings.setContinueOnError(true);
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static List<String> resolveSchemaLocations(DataSource dataSource, BatchProperties.Jdbc properties) {
|
||||
PlatformPlaceholderDatabaseDriverResolver platformResolver = new PlatformPlaceholderDatabaseDriverResolver();
|
||||
if (StringUtils.hasText(properties.getPlatform())) {
|
||||
return platformResolver.resolveAll(properties.getPlatform(), properties.getSchema());
|
||||
}
|
||||
return platformResolver.resolveAll(dataSource, properties.getSchema());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.sql.init.DatabaseInitializationMode;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
|
||||
/**
|
||||
* Configuration properties for Spring Batch.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Eddú Meléndez
|
||||
* @author Vedran Pavic
|
||||
* @author Mukul Kumar Chaundhyan
|
||||
* @author Yanming Zhou
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@ConfigurationProperties("spring.batch")
|
||||
public class BatchProperties {
|
||||
|
||||
private final Job job = new Job();
|
||||
|
||||
private final Jdbc jdbc = new Jdbc();
|
||||
|
||||
public Job getJob() {
|
||||
return this.job;
|
||||
}
|
||||
|
||||
public Jdbc getJdbc() {
|
||||
return this.jdbc;
|
||||
}
|
||||
|
||||
public static class Job {
|
||||
|
||||
/**
|
||||
* Job name to execute on startup. Must be specified if multiple Jobs are found in
|
||||
* the context.
|
||||
*/
|
||||
private String name = "";
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Jdbc {
|
||||
|
||||
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 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 String platform;
|
||||
|
||||
/**
|
||||
* Table prefix for all the batch meta-data tables.
|
||||
*/
|
||||
private 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 Isolation getIsolationLevelForCreate() {
|
||||
return this.isolationLevelForCreate;
|
||||
}
|
||||
|
||||
public void setIsolationLevelForCreate(Isolation isolationLevelForCreate) {
|
||||
this.isolationLevelForCreate = isolationLevelForCreate;
|
||||
}
|
||||
|
||||
public String getSchema() {
|
||||
return this.schema;
|
||||
}
|
||||
|
||||
public void setSchema(String schema) {
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
public String getPlatform() {
|
||||
return this.platform;
|
||||
}
|
||||
|
||||
public void setPlatform(String platform) {
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
public String getTablePrefix() {
|
||||
return this.tablePrefix;
|
||||
}
|
||||
|
||||
public void setTablePrefix(String tablePrefix) {
|
||||
this.tablePrefix = tablePrefix;
|
||||
}
|
||||
|
||||
public DatabaseInitializationMode getInitializeSchema() {
|
||||
return this.initializeSchema;
|
||||
}
|
||||
|
||||
public void setInitializeSchema(DatabaseInitializationMode initializeSchema) {
|
||||
this.initializeSchema = initializeSchema;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
|
||||
/**
|
||||
* Qualifier annotation for a {@link TaskExecutor} to be injected into Batch
|
||||
* auto-configuration. Can be used on a secondary task executor source, if there is
|
||||
* another one marked as {@link Primary @Primary}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Qualifier
|
||||
public @interface BatchTaskExecutor {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
/**
|
||||
* Qualifier annotation for a {@link PlatformTransactionManager} to be injected into Batch
|
||||
* auto-configuration. Can be used on a secondary {@link PlatformTransactionManager}, if
|
||||
* there is another one marked as {@link Primary @Primary}.
|
||||
*
|
||||
* @author Lasse Wulff
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Qualifier
|
||||
public @interface BatchTransactionManager {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.JobExecution;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* Spring {@link ApplicationEvent} encapsulating a {@link JobExecution}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class JobExecutionEvent extends ApplicationEvent {
|
||||
|
||||
private final JobExecution execution;
|
||||
|
||||
/**
|
||||
* Create a new {@link JobExecutionEvent} instance.
|
||||
* @param execution the job execution
|
||||
*/
|
||||
public JobExecutionEvent(JobExecution execution) {
|
||||
super(execution);
|
||||
this.execution = execution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the job execution.
|
||||
* @return the job execution
|
||||
*/
|
||||
public JobExecution getJobExecution() {
|
||||
return this.execution;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.boot.ExitCodeGenerator;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
|
||||
/**
|
||||
* {@link ExitCodeGenerator} for {@link JobExecutionEvent}s.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class JobExecutionExitCodeGenerator implements ApplicationListener<JobExecutionEvent>, ExitCodeGenerator {
|
||||
|
||||
private final List<JobExecution> executions = new CopyOnWriteArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(JobExecutionEvent event) {
|
||||
this.executions.add(event.getJobExecution());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
for (JobExecution execution : this.executions) {
|
||||
if (execution.getStatus().ordinal() > 0) {
|
||||
return execution.getStatus().ordinal();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobExecutionException;
|
||||
import org.springframework.batch.core.JobParameter;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.JobParametersInvalidException;
|
||||
import org.springframework.batch.core.configuration.JobRegistry;
|
||||
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
|
||||
import org.springframework.batch.core.converter.JobParametersConverter;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
|
||||
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.repository.JobRestartException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link ApplicationRunner} to {@link JobLauncher launch} Spring Batch jobs. If a single
|
||||
* job is found in the context, it will be executed by default. If multiple jobs are
|
||||
* found, launch a specific job by providing a jobName.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Jean-Pierre Bergamin
|
||||
* @author Mahmoud Ben Hassine
|
||||
* @author Stephane Nicoll
|
||||
* @author Akshay Dubey
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class JobLauncherApplicationRunner
|
||||
implements ApplicationRunner, InitializingBean, Ordered, ApplicationEventPublisherAware {
|
||||
|
||||
/**
|
||||
* The default order for the command line runner.
|
||||
*/
|
||||
public static final int DEFAULT_ORDER = 0;
|
||||
|
||||
private static final Log logger = LogFactory.getLog(JobLauncherApplicationRunner.class);
|
||||
|
||||
private JobParametersConverter converter = new DefaultJobParametersConverter();
|
||||
|
||||
private final JobLauncher jobLauncher;
|
||||
|
||||
private final JobExplorer jobExplorer;
|
||||
|
||||
private final JobRepository jobRepository;
|
||||
|
||||
private JobRegistry jobRegistry;
|
||||
|
||||
private String jobName;
|
||||
|
||||
private Collection<Job> jobs = Collections.emptySet();
|
||||
|
||||
private int order = DEFAULT_ORDER;
|
||||
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
/**
|
||||
* Create a new {@link JobLauncherApplicationRunner}.
|
||||
* @param jobLauncher to launch jobs
|
||||
* @param jobExplorer to check the job repository for previous executions
|
||||
* @param jobRepository to check if a job instance exists with the given parameters
|
||||
* when running a job
|
||||
*/
|
||||
public JobLauncherApplicationRunner(JobLauncher jobLauncher, JobExplorer jobExplorer, JobRepository jobRepository) {
|
||||
Assert.notNull(jobLauncher, "'jobLauncher' must not be null");
|
||||
Assert.notNull(jobExplorer, "'jobExplorer' must not be null");
|
||||
Assert.notNull(jobRepository, "'jobRepository' must not be null");
|
||||
this.jobLauncher = jobLauncher;
|
||||
this.jobExplorer = jobExplorer;
|
||||
this.jobRepository = jobRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.state(this.jobs.size() <= 1 || StringUtils.hasText(this.jobName),
|
||||
"Job name must be specified in case of multiple jobs");
|
||||
if (StringUtils.hasText(this.jobName)) {
|
||||
Assert.state(isLocalJob(this.jobName) || isRegisteredJob(this.jobName),
|
||||
() -> "No job found with name '" + this.jobName + "'");
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated(since = "3.0.10", forRemoval = true)
|
||||
public void validate() {
|
||||
afterPropertiesSet();
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
|
||||
this.publisher = publisher;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setJobRegistry(JobRegistry jobRegistry) {
|
||||
this.jobRegistry = jobRegistry;
|
||||
}
|
||||
|
||||
public void setJobName(String jobName) {
|
||||
this.jobName = jobName;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setJobParametersConverter(JobParametersConverter converter) {
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setJobs(Collection<Job> jobs) {
|
||||
this.jobs = jobs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
String[] jobArguments = args.getNonOptionArgs().toArray(new String[0]);
|
||||
run(jobArguments);
|
||||
}
|
||||
|
||||
public void run(String... args) throws JobExecutionException {
|
||||
logger.info("Running default command line with: " + Arrays.asList(args));
|
||||
launchJobFromProperties(StringUtils.splitArrayElementsIntoProperties(args, "="));
|
||||
}
|
||||
|
||||
protected void launchJobFromProperties(Properties properties) throws JobExecutionException {
|
||||
JobParameters jobParameters = this.converter.getJobParameters(properties);
|
||||
executeLocalJobs(jobParameters);
|
||||
executeRegisteredJobs(jobParameters);
|
||||
}
|
||||
|
||||
private boolean isLocalJob(String jobName) {
|
||||
return this.jobs.stream().anyMatch((job) -> job.getName().equals(jobName));
|
||||
}
|
||||
|
||||
private boolean isRegisteredJob(String jobName) {
|
||||
return this.jobRegistry != null && this.jobRegistry.getJobNames().contains(jobName);
|
||||
}
|
||||
|
||||
private void executeLocalJobs(JobParameters jobParameters) throws JobExecutionException {
|
||||
for (Job job : this.jobs) {
|
||||
if (StringUtils.hasText(this.jobName)) {
|
||||
if (!this.jobName.equals(job.getName())) {
|
||||
logger.debug(LogMessage.format("Skipped job: %s", job.getName()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
execute(job, jobParameters);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeRegisteredJobs(JobParameters jobParameters) throws JobExecutionException {
|
||||
if (this.jobRegistry != null && StringUtils.hasText(this.jobName)) {
|
||||
if (!isLocalJob(this.jobName)) {
|
||||
Job job = this.jobRegistry.getJob(this.jobName);
|
||||
execute(job, jobParameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void execute(Job job, JobParameters jobParameters) throws JobExecutionAlreadyRunningException,
|
||||
JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException {
|
||||
JobParameters parameters = getNextJobParameters(job, jobParameters);
|
||||
JobExecution execution = this.jobLauncher.run(job, parameters);
|
||||
if (this.publisher != null) {
|
||||
this.publisher.publishEvent(new JobExecutionEvent(execution));
|
||||
}
|
||||
}
|
||||
|
||||
private JobParameters getNextJobParameters(Job job, JobParameters jobParameters) {
|
||||
if (this.jobRepository != null && this.jobRepository.isJobInstanceExists(job.getName(), jobParameters)) {
|
||||
return getNextJobParametersForExisting(job, jobParameters);
|
||||
}
|
||||
if (job.getJobParametersIncrementer() == null) {
|
||||
return jobParameters;
|
||||
}
|
||||
JobParameters nextParameters = new JobParametersBuilder(jobParameters, this.jobExplorer)
|
||||
.getNextJobParameters(job)
|
||||
.toJobParameters();
|
||||
return merge(nextParameters, jobParameters);
|
||||
}
|
||||
|
||||
private JobParameters getNextJobParametersForExisting(Job job, JobParameters jobParameters) {
|
||||
JobExecution lastExecution = this.jobRepository.getLastJobExecution(job.getName(), jobParameters);
|
||||
if (isStoppedOrFailed(lastExecution) && job.isRestartable()) {
|
||||
JobParameters previousIdentifyingParameters = new JobParameters(
|
||||
lastExecution.getJobParameters().getIdentifyingParameters());
|
||||
return merge(previousIdentifyingParameters, jobParameters);
|
||||
}
|
||||
return jobParameters;
|
||||
}
|
||||
|
||||
private boolean isStoppedOrFailed(JobExecution execution) {
|
||||
BatchStatus status = (execution != null) ? execution.getStatus() : null;
|
||||
return (status == BatchStatus.STOPPED || status == BatchStatus.FAILED);
|
||||
}
|
||||
|
||||
private JobParameters merge(JobParameters parameters, JobParameters additionals) {
|
||||
Map<String, JobParameter<?>> merged = new LinkedHashMap<>();
|
||||
merged.putAll(parameters.getParameters());
|
||||
merged.putAll(additionals.getParameters());
|
||||
return new JobParameters(merged);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.boot.sql.init.dependency.AbstractBeansOfTypeDependsOnDatabaseInitializationDetector;
|
||||
import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector;
|
||||
|
||||
/**
|
||||
* {@link DependsOnDatabaseInitializationDetector} for Spring Batch's
|
||||
* {@link JobRepository}.
|
||||
*
|
||||
* @author Henning Pöttker
|
||||
*/
|
||||
class JobRepositoryDependsOnDatabaseInitializationDetector
|
||||
extends AbstractBeansOfTypeDependsOnDatabaseInitializationDetector {
|
||||
|
||||
@Override
|
||||
protected Set<Class<?>> getDependsOnDatabaseInitializationBeanTypes() {
|
||||
return Collections.singleton(JobRepository.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.
|
||||
*/
|
||||
package org.springframework.boot.batch.autoconfigure;
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"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": "Execute all Spring Batch jobs in the context on startup.",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Depends on Database Initialization Detectors
|
||||
org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\
|
||||
org.springframework.boot.batch.autoconfigure.JobRepositoryDependsOnDatabaseInitializationDetector
|
||||
@@ -0,0 +1 @@
|
||||
org.springframework.boot.batch.autoconfigure.BatchAutoConfiguration
|
||||
@@ -0,0 +1,905 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.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.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.configuration.DuplicateJobException;
|
||||
import org.springframework.batch.core.configuration.JobFactory;
|
||||
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.explore.JobExplorer;
|
||||
import org.springframework.batch.core.job.AbstractJob;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
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.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
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.BatchAutoConfiguration.SpringBootBatchConfiguration;
|
||||
import org.springframework.boot.batch.autoconfigure.domain.City;
|
||||
import org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration;
|
||||
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.jpa.autoconfigure.hibernate.HibernateJpaAutoConfiguration;
|
||||
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.test.system.OutputCaptureExtension;
|
||||
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.ApplicationContext;
|
||||
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.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 BatchAutoConfiguration}.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class BatchAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
|
||||
AutoConfigurations.of(BatchAutoConfiguration.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(JobLauncher.class);
|
||||
assertThat(context).hasSingleBean(JobExplorer.class);
|
||||
assertThat(context).hasSingleBean(JobRegistry.class);
|
||||
assertThat(context).hasSingleBean(JobOperator.class);
|
||||
assertThat(context.getBean(BatchProperties.class).getJdbc().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(SpringBootBatchConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoConfigurationBacksOffWhenUserProvidesBatchConfiguration() {
|
||||
this.contextRunner.withUserConfiguration(CustomBatchConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||
.withClassLoader(new FilteredClassLoader(DatabasePopulator.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(SpringBootBatchConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefinesAndLaunchesJob() {
|
||||
this.contextRunner.withUserConfiguration(JobConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(JobLauncher.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(JobLauncher.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 testDefinesAndLaunchesNamedRegisteredJob() {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(NamedJobConfigurationWithRegisteredJob.class, EmbeddedDataSourceConfiguration.class)
|
||||
.withPropertyValues("spring.batch.job.name:discreteRegisteredJob")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(JobLauncher.class);
|
||||
context.getBean(JobLauncherApplicationRunner.class).run();
|
||||
assertThat(context.getBean(JobRepository.class)
|
||||
.getLastJobExecution("discreteRegisteredJob", new JobParameters())).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegisteredAndLocalJob() {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(NamedJobConfigurationWithRegisteredAndLocalJob.class,
|
||||
EmbeddedDataSourceConfiguration.class)
|
||||
.withPropertyValues("spring.batch.job.name:discreteRegisteredJob")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(JobLauncher.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(JobLauncher.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(JobLauncher.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(JobLauncher.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(JobLauncher.class);
|
||||
assertThat(context.getBean(BatchProperties.class).getJdbc().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(JobLauncher.class);
|
||||
assertThat(context.getBean(BatchProperties.class).getJdbc().getInitializeSchema())
|
||||
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
|
||||
assertThat(new JdbcTemplate(context.getBean(DataSource.class))
|
||||
.queryForList("select * from PREFIX_JOB_EXECUTION")).isEmpty();
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
assertThat(jobExplorer.findRunningJobExecutions("test")).isEmpty();
|
||||
JobRepository jobRepository = context.getBean(JobRepository.class);
|
||||
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(BatchAutoConfiguration.class);
|
||||
JpaTransactionManager transactionManager = JpaTransactionManager.class
|
||||
.cast(context.getBean(SpringBootBatchConfiguration.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(SpringBootBatchConfiguration.class);
|
||||
DataSourceTransactionManager transactionManager = DataSourceTransactionManager.class
|
||||
.cast(context.getBean(SpringBootBatchConfiguration.class).getTransactionManager());
|
||||
assertThat(transactionManager.getDefaultTimeout()).isEqualTo(30);
|
||||
assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBatchDataSource() {
|
||||
this.contextRunner.withUserConfiguration(BatchDataSourceConfiguration.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(SpringBootBatchConfiguration.class)
|
||||
.hasSingleBean(BatchDataSourceScriptDatabaseInitializer.class)
|
||||
.hasBean("batchDataSource");
|
||||
DataSource batchDataSource = context.getBean("batchDataSource", DataSource.class);
|
||||
assertThat(context.getBean(SpringBootBatchConfiguration.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(SpringBootBatchConfiguration.class);
|
||||
PlatformTransactionManager batchTransactionManager = context.getBean("batchTransactionManager",
|
||||
PlatformTransactionManager.class);
|
||||
assertThat(context.getBean(SpringBootBatchConfiguration.class).getTransactionManager())
|
||||
.isEqualTo(batchTransactionManager);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBatchTaskExecutor() {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(BatchTaskExecutorConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(SpringBootBatchConfiguration.class).hasBean("batchTaskExecutor");
|
||||
TaskExecutor batchTaskExecutor = context.getBean("batchTaskExecutor", TaskExecutor.class);
|
||||
assertThat(batchTaskExecutor).isInstanceOf(AsyncTaskExecutor.class);
|
||||
assertThat(context.getBean(SpringBootBatchConfiguration.class).getTaskExecutor())
|
||||
.isEqualTo(batchTaskExecutor);
|
||||
assertThat(context.getBean(JobLauncher.class)).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(SpringBootBatchConfiguration.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(SpringBootBatchConfiguration.class).getExecutionContextSerializer())
|
||||
.isInstanceOf(Jackson2ExecutionContextStringSerializer.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultExecutionContextSerializerIsUsed() {
|
||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(ExecutionContextSerializer.class);
|
||||
assertThat(context.getBean(SpringBootBatchConfiguration.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) -> {
|
||||
SpringBootBatchConfiguration configuration = context.getBean(SpringBootBatchConfiguration.class);
|
||||
assertThat(configuration.getValidateTransactionState()).isEqualTo(false);
|
||||
assertThat(configuration.getIsolationLevelForCreate()).isEqualTo(Isolation.READ_COMMITTED);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
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(SpringBootBatchConfiguration.class).getJobParametersConverter())
|
||||
.isInstanceOf(JsonJobParametersConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultJobParametersConverterIsUsed() {
|
||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(JobParametersConverter.class);
|
||||
assertThat(context.getBean(SpringBootBatchConfiguration.class).getJobParametersConverter())
|
||||
.isInstanceOf(DefaultJobParametersConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
private JobLauncherApplicationRunner createInstance(String... registeredJobNames) {
|
||||
JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(mock(JobLauncher.class),
|
||||
mock(JobExplorer.class), mock(JobRepository.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 NamedJobConfigurationWithRegisteredJob {
|
||||
|
||||
@Bean
|
||||
static BeanPostProcessor registryProcessor(ApplicationContext applicationContext) {
|
||||
return new NamedJobJobRegistryBeanPostProcessor(applicationContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class NamedJobJobRegistryBeanPostProcessor implements BeanPostProcessor {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
NamedJobJobRegistryBeanPostProcessor(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof JobRegistry jobRegistry) {
|
||||
try {
|
||||
jobRegistry.register(getJobFactory());
|
||||
}
|
||||
catch (DuplicateJobException ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
private JobFactory getJobFactory() {
|
||||
JobRepository jobRepository = this.applicationContext.getBean(JobRepository.class);
|
||||
return new JobFactory() {
|
||||
|
||||
@Override
|
||||
public Job createJob() {
|
||||
AbstractJob job = new AbstractJob("discreteRegisteredJob") {
|
||||
|
||||
@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(jobRepository);
|
||||
return job;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJobName() {
|
||||
return "discreteRegisteredJob";
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@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, BatchProperties properties) {
|
||||
return new BatchDataSourceScriptDatabaseInitializer(dataSource, properties.getJdbc());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@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,103 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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 javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
|
||||
import org.springframework.boot.batch.autoconfigure.BatchAutoConfiguration.SpringBootBatchConfiguration;
|
||||
import org.springframework.boot.batch.autoconfigure.domain.City;
|
||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
|
||||
import org.springframework.boot.jdbc.autoconfigure.EmbeddedDataSourceConfiguration;
|
||||
import org.springframework.boot.sql.init.DatabaseInitializationMode;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
|
||||
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources;
|
||||
import org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link BatchAutoConfiguration} when JPA is not on the classpath.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@ClassPathExclusions("hibernate-jpa-*.jar")
|
||||
class BatchAutoConfigurationWithoutJpaTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(BatchAutoConfiguration.class, TransactionAutoConfiguration.class,
|
||||
DataSourceTransactionManagerAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void jdbcWithDefaultSettings() {
|
||||
this.contextRunner.withUserConfiguration(DefaultConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||
.withPropertyValues("spring.datasource.generate-unique-name=true")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(JobLauncher.class);
|
||||
assertThat(context).hasSingleBean(JobExplorer.class);
|
||||
assertThat(context).hasSingleBean(JobRepository.class);
|
||||
assertThat(context.getBean(BatchProperties.class).getJdbc().getInitializeSchema())
|
||||
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
|
||||
assertThat(new JdbcTemplate(context.getBean(DataSource.class))
|
||||
.queryForList("select * from BATCH_JOB_EXECUTION")).isEmpty();
|
||||
assertThat(context.getBean(JobExplorer.class).findRunningJobExecutions("test")).isEmpty();
|
||||
assertThat(context.getBean(JobRepository.class).getLastJobExecution("test", new JobParameters()))
|
||||
.isNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("custom-schema.sql")
|
||||
void jdbcWithCustomPrefix() {
|
||||
this.contextRunner.withUserConfiguration(DefaultConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||
.withPropertyValues("spring.datasource.generate-unique-name=true",
|
||||
"spring.batch.jdbc.schema:classpath:custom-schema.sql", "spring.batch.jdbc.tablePrefix:PREFIX_")
|
||||
.run((context) -> {
|
||||
assertThat(new JdbcTemplate(context.getBean(DataSource.class))
|
||||
.queryForList("select * from PREFIX_JOB_EXECUTION")).isEmpty();
|
||||
assertThat(context.getBean(JobExplorer.class).findRunningJobExecutions("test")).isEmpty();
|
||||
assertThat(context.getBean(JobRepository.class).getLastJobExecution("test", new JobParameters()))
|
||||
.isNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void jdbcWithCustomIsolationLevel() {
|
||||
this.contextRunner.withUserConfiguration(DefaultConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||
.withPropertyValues("spring.datasource.generate-unique-name=true",
|
||||
"spring.batch.jdbc.isolation-level-for-create=read_committed")
|
||||
.run((context) -> assertThat(
|
||||
context.getBean(SpringBootBatchConfiguration.class).getIsolationLevelForCreate())
|
||||
.isEqualTo(Isolation.READ_COMMITTED));
|
||||
}
|
||||
|
||||
@TestAutoConfigurationPackage(City.class)
|
||||
static class DefaultConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.jupiter.params.provider.EnumSource.Mode;
|
||||
|
||||
import org.springframework.boot.jdbc.DatabaseDriver;
|
||||
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link BatchDataSourceScriptDatabaseInitializer}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class BatchDataSourceScriptDatabaseInitializerTests {
|
||||
|
||||
@Test
|
||||
void getSettingsWithPlatformDoesNotTouchDataSource() {
|
||||
DataSource dataSource = mock(DataSource.class);
|
||||
BatchProperties properties = new BatchProperties();
|
||||
properties.getJdbc().setPlatform("test");
|
||||
DatabaseInitializationSettings settings = BatchDataSourceScriptDatabaseInitializer.getSettings(dataSource,
|
||||
properties.getJdbc());
|
||||
assertThat(settings.getSchemaLocations())
|
||||
.containsOnly("classpath:org/springframework/batch/core/schema-test.sql");
|
||||
then(dataSource).shouldHaveNoInteractions();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(value = DatabaseDriver.class, mode = Mode.EXCLUDE, names = { "AWS_WRAPPER", "CLICKHOUSE", "FIREBIRD",
|
||||
"INFORMIX", "JTDS", "PHOENIX", "REDSHIFT", "TERADATA", "TESTCONTAINERS", "UNKNOWN" })
|
||||
void batchSchemaCanBeLocated(DatabaseDriver driver) throws SQLException {
|
||||
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||
BatchProperties properties = new BatchProperties();
|
||||
DataSource dataSource = mock(DataSource.class);
|
||||
Connection connection = mock(Connection.class);
|
||||
given(dataSource.getConnection()).willReturn(connection);
|
||||
DatabaseMetaData metadata = mock(DatabaseMetaData.class);
|
||||
given(connection.getMetaData()).willReturn(metadata);
|
||||
String productName = (String) ReflectionTestUtils.getField(driver, "productName");
|
||||
given(metadata.getDatabaseProductName()).willReturn(productName);
|
||||
DatabaseInitializationSettings settings = BatchDataSourceScriptDatabaseInitializer.getSettings(dataSource,
|
||||
properties.getJdbc());
|
||||
List<String> schemaLocations = settings.getSchemaLocations();
|
||||
assertThat(schemaLocations).isNotEmpty()
|
||||
.allSatisfy((location) -> assertThat(resourceLoader.getResource(location).exists()).isTrue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchHasExpectedBuiltInSchemas() throws IOException {
|
||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
List<String> schemaNames = Stream
|
||||
.of(resolver.getResources("classpath:org/springframework/batch/core/schema-*.sql"))
|
||||
.map(Resource::getFilename)
|
||||
.filter((resourceName) -> !resourceName.contains("-drop-"))
|
||||
.toList();
|
||||
assertThat(schemaNames).containsExactlyInAnyOrder("schema-derby.sql", "schema-sqlserver.sql",
|
||||
"schema-mariadb.sql", "schema-mysql.sql", "schema-sqlite.sql", "schema-postgresql.sql",
|
||||
"schema-hana.sql", "schema-oracle.sql", "schema-db2.sql", "schema-hsqldb.sql", "schema-sybase.sql",
|
||||
"schema-h2.sql");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link BatchProperties}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class BatchPropertiesTests {
|
||||
|
||||
@Test
|
||||
void validateTransactionStateDefaultMatchesSpringBatchDefault() {
|
||||
assertThat(new BatchProperties().getJdbc().isValidateTransactionState())
|
||||
.isEqualTo(new TestBatchConfiguration().getValidateTransactionState());
|
||||
}
|
||||
|
||||
static class TestBatchConfiguration extends DefaultBatchConfiguration {
|
||||
|
||||
@Override
|
||||
public boolean getValidateTransactionState() {
|
||||
return super.getValidateTransactionState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JobExecutionExitCodeGenerator}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
class JobExecutionExitCodeGeneratorTests {
|
||||
|
||||
private final JobExecutionExitCodeGenerator generator = new JobExecutionExitCodeGenerator();
|
||||
|
||||
@Test
|
||||
void testExitCodeForRunning() {
|
||||
this.generator.onApplicationEvent(new JobExecutionEvent(new JobExecution(0L)));
|
||||
assertThat(this.generator.getExitCode()).isOne();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExitCodeForCompleted() {
|
||||
JobExecution execution = new JobExecution(0L);
|
||||
execution.setStatus(BatchStatus.COMPLETED);
|
||||
this.generator.onApplicationEvent(new JobExecutionEvent(execution));
|
||||
assertThat(this.generator.getExitCode()).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExitCodeForFailed() {
|
||||
JobExecution execution = new JobExecution(0L);
|
||||
execution.setStatus(BatchStatus.FAILED);
|
||||
this.generator.onApplicationEvent(new JobExecutionEvent(execution));
|
||||
assertThat(this.generator.getExitCode()).isEqualTo(5);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecutionException;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.job.builder.JobBuilder;
|
||||
import org.springframework.batch.core.job.builder.SimpleJobBuilder;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.repository.JobRestartException;
|
||||
import org.springframework.batch.core.step.builder.StepBuilder;
|
||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
|
||||
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
|
||||
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* Tests for {@link JobLauncherApplicationRunner}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Jean-Pierre Bergamin
|
||||
* @author Mahmoud Ben Hassine
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class JobLauncherApplicationRunnerTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, TransactionAutoConfiguration.class,
|
||||
DataSourceTransactionManagerAutoConfiguration.class))
|
||||
.withUserConfiguration(BatchConfiguration.class);
|
||||
|
||||
@Test
|
||||
void basicExecution() {
|
||||
this.contextRunner.run((context) -> {
|
||||
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
|
||||
jobLauncherContext.executeJob(new JobParameters());
|
||||
assertThat(jobLauncherContext.jobInstances()).hasSize(1);
|
||||
jobLauncherContext.executeJob(new JobParametersBuilder().addLong("id", 1L).toJobParameters());
|
||||
assertThat(jobLauncherContext.jobInstances()).hasSize(2);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementExistingExecution() {
|
||||
this.contextRunner.run((context) -> {
|
||||
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
|
||||
Job job = jobLauncherContext.configureJob().incrementer(new RunIdIncrementer()).build();
|
||||
jobLauncherContext.runner.execute(job, new JobParameters());
|
||||
jobLauncherContext.runner.execute(job, new JobParameters());
|
||||
assertThat(jobLauncherContext.jobInstances()).hasSize(2);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryFailedExecution() {
|
||||
this.contextRunner.run((context) -> {
|
||||
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
|
||||
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
|
||||
Job job = jobLauncherContext.jobBuilder()
|
||||
.start(jobLauncherContext.stepBuilder().tasklet(throwingTasklet(), transactionManager).build())
|
||||
.incrementer(new RunIdIncrementer())
|
||||
.build();
|
||||
jobLauncherContext.runner.execute(job, new JobParameters());
|
||||
jobLauncherContext.runner.execute(job, new JobParametersBuilder().addLong("run.id", 1L).toJobParameters());
|
||||
assertThat(jobLauncherContext.jobInstances()).hasSize(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void runDifferentInstances() {
|
||||
this.contextRunner.run((context) -> {
|
||||
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
|
||||
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
|
||||
Job job = jobLauncherContext.jobBuilder()
|
||||
.start(jobLauncherContext.stepBuilder().tasklet(throwingTasklet(), transactionManager).build())
|
||||
.build();
|
||||
// start a job instance
|
||||
JobParameters jobParameters = new JobParametersBuilder().addString("name", "foo").toJobParameters();
|
||||
jobLauncherContext.runner.execute(job, jobParameters);
|
||||
assertThat(jobLauncherContext.jobInstances()).hasSize(1);
|
||||
// start a different job instance
|
||||
JobParameters otherJobParameters = new JobParametersBuilder().addString("name", "bar").toJobParameters();
|
||||
jobLauncherContext.runner.execute(job, otherJobParameters);
|
||||
assertThat(jobLauncherContext.jobInstances()).hasSize(2);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryFailedExecutionOnNonRestartableJob() {
|
||||
this.contextRunner.run((context) -> {
|
||||
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
|
||||
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
|
||||
Job job = jobLauncherContext.jobBuilder()
|
||||
.preventRestart()
|
||||
.start(jobLauncherContext.stepBuilder().tasklet(throwingTasklet(), transactionManager).build())
|
||||
.incrementer(new RunIdIncrementer())
|
||||
.build();
|
||||
jobLauncherContext.runner.execute(job, new JobParameters());
|
||||
jobLauncherContext.runner.execute(job, new JobParameters());
|
||||
// A failed job that is not restartable does not re-use the job params of
|
||||
// the last execution, but creates a new job instance when running it again.
|
||||
assertThat(jobLauncherContext.jobInstances()).hasSize(2);
|
||||
assertThatExceptionOfType(JobRestartException.class).isThrownBy(() -> {
|
||||
// try to re-run a failed execution
|
||||
jobLauncherContext.runner.execute(job,
|
||||
new JobParametersBuilder().addLong("run.id", 1L).toJobParameters());
|
||||
fail("expected JobRestartException");
|
||||
}).withMessageContaining("JobInstance already exists and is not restartable");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryFailedExecutionWithNonIdentifyingParameters() {
|
||||
this.contextRunner.run((context) -> {
|
||||
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
|
||||
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
|
||||
Job job = jobLauncherContext.jobBuilder()
|
||||
.start(jobLauncherContext.stepBuilder().tasklet(throwingTasklet(), transactionManager).build())
|
||||
.incrementer(new RunIdIncrementer())
|
||||
.build();
|
||||
JobParameters jobParameters = new JobParametersBuilder().addLong("id", 1L, false)
|
||||
.addLong("foo", 2L, false)
|
||||
.toJobParameters();
|
||||
jobLauncherContext.runner.execute(job, jobParameters);
|
||||
assertThat(jobLauncherContext.jobInstances()).hasSize(1);
|
||||
// try to re-run a failed execution with non identifying parameters
|
||||
jobLauncherContext.runner.execute(job,
|
||||
new JobParametersBuilder(jobParameters).addLong("run.id", 1L).toJobParameters());
|
||||
assertThat(jobLauncherContext.jobInstances()).hasSize(1);
|
||||
});
|
||||
}
|
||||
|
||||
private Tasklet throwingTasklet() {
|
||||
return (contribution, chunkContext) -> {
|
||||
throw new RuntimeException("Planned");
|
||||
};
|
||||
}
|
||||
|
||||
static class JobLauncherApplicationRunnerContext {
|
||||
|
||||
private final JobLauncherApplicationRunner runner;
|
||||
|
||||
private final JobExplorer jobExplorer;
|
||||
|
||||
private final JobBuilder jobBuilder;
|
||||
|
||||
private final Job job;
|
||||
|
||||
private final StepBuilder stepBuilder;
|
||||
|
||||
private final Step step;
|
||||
|
||||
JobLauncherApplicationRunnerContext(ApplicationContext context) {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
JobRepository jobRepository = context.getBean(JobRepository.class);
|
||||
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
|
||||
this.stepBuilder = new StepBuilder("step", jobRepository);
|
||||
this.step = this.stepBuilder.tasklet((contribution, chunkContext) -> null, transactionManager).build();
|
||||
this.jobBuilder = new JobBuilder("job", jobRepository);
|
||||
this.job = this.jobBuilder.start(this.step).build();
|
||||
this.jobExplorer = context.getBean(JobExplorer.class);
|
||||
this.runner = new JobLauncherApplicationRunner(jobLauncher, this.jobExplorer, jobRepository);
|
||||
}
|
||||
|
||||
List<JobInstance> jobInstances() {
|
||||
return this.jobExplorer.getJobInstances("job", 0, 100);
|
||||
}
|
||||
|
||||
void executeJob(JobParameters jobParameters) throws JobExecutionException {
|
||||
this.runner.execute(this.job, jobParameters);
|
||||
}
|
||||
|
||||
JobBuilder jobBuilder() {
|
||||
return this.jobBuilder;
|
||||
}
|
||||
|
||||
StepBuilder stepBuilder() {
|
||||
return this.stepBuilder;
|
||||
}
|
||||
|
||||
SimpleJobBuilder configureJob() {
|
||||
return this.jobBuilder.start(this.step);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBatchProcessing
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class BatchConfiguration {
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
protected BatchConfiguration(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
DataSourceScriptDatabaseInitializer batchDataSourceInitializer() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setSchemaLocations(Arrays.asList("classpath:org/springframework/batch/core/schema-h2.sql"));
|
||||
return new DataSourceScriptDatabaseInitializer(this.dataSource, settings);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class City implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String state;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String country;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String map;
|
||||
|
||||
protected City() {
|
||||
}
|
||||
|
||||
public City(String name, String state, String country, String map) {
|
||||
this.name = name;
|
||||
this.state = state;
|
||||
this.country = country;
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
return this.country;
|
||||
}
|
||||
|
||||
public String getMap() {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName() + "," + getState() + "," + getCountry();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
CREATE TABLE PREFIX_JOB_INSTANCE (
|
||||
JOB_INSTANCE_ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
VERSION BIGINT,
|
||||
JOB_NAME VARCHAR(100) NOT NULL,
|
||||
JOB_KEY VARCHAR(32) NOT NULL,
|
||||
constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY)
|
||||
) ;
|
||||
|
||||
CREATE TABLE PREFIX_JOB_EXECUTION (
|
||||
JOB_EXECUTION_ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
VERSION BIGINT,
|
||||
JOB_INSTANCE_ID BIGINT NOT NULL,
|
||||
CREATE_TIME TIMESTAMP NOT NULL,
|
||||
START_TIME TIMESTAMP DEFAULT NULL,
|
||||
END_TIME TIMESTAMP DEFAULT NULL,
|
||||
STATUS VARCHAR(10),
|
||||
EXIT_CODE VARCHAR(2500),
|
||||
EXIT_MESSAGE VARCHAR(2500),
|
||||
LAST_UPDATED TIMESTAMP,
|
||||
JOB_CONFIGURATION_LOCATION VARCHAR(2500) NULL,
|
||||
constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID)
|
||||
references PREFIX_JOB_INSTANCE(JOB_INSTANCE_ID)
|
||||
) ;
|
||||
|
||||
CREATE TABLE PREFIX_JOB_EXECUTION_PARAMS (
|
||||
JOB_EXECUTION_ID BIGINT NOT NULL,
|
||||
TYPE_CD VARCHAR(6) NOT NULL,
|
||||
KEY_NAME VARCHAR(100) NOT NULL,
|
||||
STRING_VAL VARCHAR(250),
|
||||
DATE_VAL TIMESTAMP DEFAULT NULL,
|
||||
LONG_VAL BIGINT,
|
||||
DOUBLE_VAL DOUBLE PRECISION,
|
||||
IDENTIFYING CHAR(1) NOT NULL,
|
||||
constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID)
|
||||
references PREFIX_JOB_EXECUTION(JOB_EXECUTION_ID)
|
||||
) ;
|
||||
|
||||
CREATE TABLE PREFIX_STEP_EXECUTION (
|
||||
STEP_EXECUTION_ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
VERSION BIGINT NOT NULL,
|
||||
STEP_NAME VARCHAR(100) NOT NULL,
|
||||
JOB_EXECUTION_ID BIGINT NOT NULL,
|
||||
START_TIME TIMESTAMP NOT NULL,
|
||||
END_TIME TIMESTAMP DEFAULT NULL,
|
||||
STATUS VARCHAR(10),
|
||||
COMMIT_COUNT BIGINT,
|
||||
READ_COUNT BIGINT,
|
||||
FILTER_COUNT BIGINT,
|
||||
WRITE_COUNT BIGINT,
|
||||
READ_SKIP_COUNT BIGINT,
|
||||
WRITE_SKIP_COUNT BIGINT,
|
||||
PROCESS_SKIP_COUNT BIGINT,
|
||||
ROLLBACK_COUNT BIGINT,
|
||||
EXIT_CODE VARCHAR(2500),
|
||||
EXIT_MESSAGE VARCHAR(2500),
|
||||
LAST_UPDATED TIMESTAMP,
|
||||
constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID)
|
||||
references PREFIX_JOB_EXECUTION(JOB_EXECUTION_ID)
|
||||
) ;
|
||||
|
||||
CREATE TABLE PREFIX_STEP_EXECUTION_CONTEXT (
|
||||
STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
|
||||
SHORT_CONTEXT VARCHAR(2500) NOT NULL,
|
||||
SERIALIZED_CONTEXT LONGVARCHAR,
|
||||
constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID)
|
||||
references PREFIX_STEP_EXECUTION(STEP_EXECUTION_ID)
|
||||
) ;
|
||||
|
||||
CREATE TABLE PREFIX_JOB_EXECUTION_CONTEXT (
|
||||
JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
|
||||
SHORT_CONTEXT VARCHAR(2500) NOT NULL,
|
||||
SERIALIZED_CONTEXT LONGVARCHAR,
|
||||
constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID)
|
||||
references PREFIX_JOB_EXECUTION(JOB_EXECUTION_ID)
|
||||
) ;
|
||||
|
||||
CREATE TABLE PREFIX_STEP_EXECUTION_SEQ (
|
||||
ID BIGINT GENERATED BY DEFAULT AS IDENTITY
|
||||
);
|
||||
CREATE TABLE PREFIX_JOB_EXECUTION_SEQ (
|
||||
ID BIGINT GENERATED BY DEFAULT AS IDENTITY
|
||||
);
|
||||
CREATE TABLE PREFIX_JOB_SEQ (
|
||||
ID BIGINT GENERATED BY DEFAULT AS IDENTITY
|
||||
);
|
||||
Reference in New Issue
Block a user