Add support for in-memory Batch infrastructure
This commit moves the existing JDBC-based Spring Batch infrastructure to a new 'spring-boot-batch-jdbc' module, while the existing module only offers in-memory (aka resourceless) support. The commit also updates the reference guide to provide some more information about what's available and how to use it. Closes gh-46307
This commit is contained in:
parent
54ffc42309
commit
4f6bbac13e
|
@ -387,8 +387,6 @@
|
||||||
* xref:how-to:batch.adoc#howto.batch.restarting-a-failed-job[#howto.batch.restarting-a-failed-job]
|
* xref:how-to:batch.adoc#howto.batch.restarting-a-failed-job[#howto.batch.restarting-a-failed-job]
|
||||||
* xref:how-to:batch.adoc#howto.batch.running-from-the-command-line[#howto-spring-batch-running-command-line]
|
* xref:how-to:batch.adoc#howto.batch.running-from-the-command-line[#howto-spring-batch-running-command-line]
|
||||||
* xref:how-to:batch.adoc#howto.batch.running-from-the-command-line[#howto.batch.running-from-the-command-line]
|
* xref:how-to:batch.adoc#howto.batch.running-from-the-command-line[#howto.batch.running-from-the-command-line]
|
||||||
* xref:how-to:batch.adoc#howto.batch.running-jobs-on-startup[#howto-spring-batch-running-jobs-on-startup]
|
|
||||||
* xref:how-to:batch.adoc#howto.batch.running-jobs-on-startup[#howto.batch.running-jobs-on-startup]
|
|
||||||
* xref:how-to:batch.adoc#howto.batch.specifying-a-data-source[#howto-spring-batch-specifying-a-data-source]
|
* xref:how-to:batch.adoc#howto.batch.specifying-a-data-source[#howto-spring-batch-specifying-a-data-source]
|
||||||
* xref:how-to:batch.adoc#howto.batch.specifying-a-data-source[#howto.batch.specifying-a-data-source]
|
* xref:how-to:batch.adoc#howto.batch.specifying-a-data-source[#howto.batch.specifying-a-data-source]
|
||||||
* xref:how-to:batch.adoc#howto.batch.specifying-a-transaction-manager[#howto.batch.specifying-a-transaction-manager]
|
* xref:how-to:batch.adoc#howto.batch.specifying-a-transaction-manager[#howto.batch.specifying-a-transaction-manager]
|
||||||
|
@ -1633,6 +1631,8 @@
|
||||||
* xref:reference:features/ssl.adoc#features.ssl[#features.ssl]
|
* xref:reference:features/ssl.adoc#features.ssl[#features.ssl]
|
||||||
* xref:reference:features/task-execution-and-scheduling.adoc#features.task-execution-and-scheduling[#boot-features-task-execution-scheduling]
|
* xref:reference:features/task-execution-and-scheduling.adoc#features.task-execution-and-scheduling[#boot-features-task-execution-scheduling]
|
||||||
* xref:reference:features/task-execution-and-scheduling.adoc#features.task-execution-and-scheduling[#features.task-execution-and-scheduling]
|
* xref:reference:features/task-execution-and-scheduling.adoc#features.task-execution-and-scheduling[#features.task-execution-and-scheduling]
|
||||||
|
* xref:reference:io/spring-batch.adoc#io.spring-batch.running-jobs-on-startup[#howto-spring-batch-running-jobs-on-startup]
|
||||||
|
* xref:reference:io/spring-batch.adoc#io.spring-batch.running-jobs-on-startup[#howto.batch.running-jobs-on-startup]
|
||||||
* xref:reference:io/caching.adoc#io.caching.provider.cache2k[#io.caching.provider.cache2k]
|
* xref:reference:io/caching.adoc#io.caching.provider.cache2k[#io.caching.provider.cache2k]
|
||||||
* xref:reference:io/caching.adoc#io.caching.provider.caffeine[#boot-features-caching-provider-caffeine]
|
* xref:reference:io/caching.adoc#io.caching.provider.caffeine[#boot-features-caching-provider-caffeine]
|
||||||
* xref:reference:io/caching.adoc#io.caching.provider.caffeine[#features.caching.provider.caffeine]
|
* xref:reference:io/caching.adoc#io.caching.provider.caffeine[#features.caching.provider.caffeine]
|
||||||
|
|
|
@ -37,20 +37,6 @@ If you do so and want two task executors (for example by retaining the auto-conf
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[howto.batch.running-jobs-on-startup]]
|
|
||||||
== Running Spring Batch Jobs on Startup
|
|
||||||
|
|
||||||
Spring Batch auto-configuration is enabled by adding `spring-boot-starter-batch` to your application's classpath.
|
|
||||||
|
|
||||||
If a single javadoc:org.springframework.batch.core.Job[] bean is found in the application context, it is executed on startup (see javadoc:org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner[] for details).
|
|
||||||
If multiple javadoc:org.springframework.batch.core.Job[] beans are found, the job that should be executed must be specified using configprop:spring.batch.job.name[].
|
|
||||||
|
|
||||||
To disable running a javadoc:org.springframework.batch.core.Job[] found in the application context, set the configprop:spring.batch.job.enabled[] to `false`.
|
|
||||||
|
|
||||||
See {code-spring-boot-autoconfigure-src}/batch/BatchAutoConfiguration.java[`BatchAutoConfiguration`] for more details.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[howto.batch.running-from-the-command-line]]
|
[[howto.batch.running-from-the-command-line]]
|
||||||
== Running From the Command Line
|
== Running From the Command Line
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
|
|
||||||
Most applications will need to deal with input and output concerns at some point.
|
Most applications will need to deal with input and output concerns at some point.
|
||||||
Spring Boot provides utilities and integrations with a range of technologies to help when you need IO capabilities.
|
Spring Boot provides utilities and integrations with a range of technologies to help when you need IO capabilities.
|
||||||
This section covers standard IO features such as caching and validation as well as more advanced topics such as scheduling and distributed transactions.
|
This section covers standard IO features such as caching and validation as well as more advanced topics such as batch, scheduling, and distributed transactions.
|
||||||
We will also cover calling remote REST or SOAP services and sending email.
|
We will also cover calling remote REST or SOAP services and sending email.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
[[io.spring-batch]]
|
||||||
|
= Spring Batch
|
||||||
|
|
||||||
|
Spring Boot offers several conveniences for working with {url-spring-batch-site}[Spring Batch], including running a Job on startup.
|
||||||
|
|
||||||
|
If Spring Batch is available on your classpath, it is initialized through the javadoc:org.springframework.batch.core.configuration.annotation.EnableBatchProcessing[format=annotation] annotation.
|
||||||
|
|
||||||
|
When building a batch application, the following stores can be auto-configured:
|
||||||
|
|
||||||
|
* In-memory
|
||||||
|
* JDBC
|
||||||
|
|
||||||
|
Each store has specific additional settings.
|
||||||
|
For instance, it is possible to customize the tables prefix for the JDBC store, as shown in the following example:
|
||||||
|
|
||||||
|
[configprops,yaml]
|
||||||
|
----
|
||||||
|
spring:
|
||||||
|
batch:
|
||||||
|
jdbc:
|
||||||
|
table-prefix: "CUSTOM_"
|
||||||
|
----
|
||||||
|
|
||||||
|
You can take control over Spring Batch's configuration using javadoc:org.springframework.batch.core.configuration.annotation.EnableBatchProcessing[format=annotation].
|
||||||
|
This will cause the auto-configuration to back off.
|
||||||
|
Spring Batch can then be configured using the `@Enable*JobRepository` annotation's attributes rather than the previously described configuration properties.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[io.spring-batch.running-jobs-on-startup]]
|
||||||
|
== Running Spring Batch Jobs on Startup
|
||||||
|
|
||||||
|
When Spring Boot auto-configures Spring Batch, and if a single javadoc:org.springframework.batch.core.Job[] bean is found in the application context, it is executed on startup (see javadoc:org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner[] for details).
|
||||||
|
If multiple javadoc:org.springframework.batch.core.Job[] beans are found, the job that should be executed must be specified using configprop:spring.batch.job.name[].
|
||||||
|
|
||||||
|
You can disable running a javadoc:org.springframework.batch.core.Job[] found in the application context, as shown in the following example:
|
||||||
|
|
||||||
|
[configprops,yaml]
|
||||||
|
----
|
||||||
|
spring:
|
||||||
|
batch:
|
||||||
|
job:
|
||||||
|
enabled: false
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
See javadoc:org.springframework.boot.batch.autoconfigure.BatchAutoConfiguration[] and javadoc:org.springframework.boot.batch.jdbc.autoconfigure.BatchJdbcAutoConfiguration[] for more details.
|
|
@ -39,6 +39,7 @@
|
||||||
|
|
||||||
** xref:reference:io/index.adoc[]
|
** xref:reference:io/index.adoc[]
|
||||||
*** xref:reference:io/caching.adoc[]
|
*** xref:reference:io/caching.adoc[]
|
||||||
|
*** xref:reference:io/spring-batch.adoc[]
|
||||||
*** xref:reference:io/hazelcast.adoc[]
|
*** xref:reference:io/hazelcast.adoc[]
|
||||||
*** xref:reference:io/quartz.adoc[]
|
*** xref:reference:io/quartz.adoc[]
|
||||||
*** xref:reference:io/email.adoc[]
|
*** xref:reference:io/email.adoc[]
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the License);
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "java-library"
|
||||||
|
id "org.springframework.boot.auto-configuration"
|
||||||
|
id "org.springframework.boot.configuration-properties"
|
||||||
|
id "org.springframework.boot.deployed"
|
||||||
|
id "org.springframework.boot.optional-dependencies"
|
||||||
|
}
|
||||||
|
|
||||||
|
description = "Spring Boot Batch JDBC"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(":module:spring-boot-batch"))
|
||||||
|
api(project(":module:spring-boot-jdbc"))
|
||||||
|
|
||||||
|
implementation(project(":module:spring-boot-tx"))
|
||||||
|
|
||||||
|
optional(project(":core:spring-boot-autoconfigure"))
|
||||||
|
optional(project(":module:spring-boot-hibernate"))
|
||||||
|
optional(project(":module:spring-boot-micrometer-observation"))
|
||||||
|
|
||||||
|
testImplementation(project(":core:spring-boot-test"))
|
||||||
|
testImplementation(project(":module:spring-boot-flyway"))
|
||||||
|
testImplementation(project(":module:spring-boot-liquibase"))
|
||||||
|
testImplementation(project(":test-support:spring-boot-test-support"))
|
||||||
|
testImplementation(testFixtures(project(":core:spring-boot-autoconfigure")))
|
||||||
|
testImplementation("io.micrometer:micrometer-observation-test")
|
||||||
|
|
||||||
|
testRuntimeOnly("ch.qos.logback:logback-classic")
|
||||||
|
testRuntimeOnly("com.fasterxml.jackson.core:jackson-databind")
|
||||||
|
testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
|
||||||
|
testRuntimeOnly("com.h2database:h2")
|
||||||
|
testRuntimeOnly("com.zaxxer:HikariCP")
|
||||||
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.boot.batch.autoconfigure;
|
package org.springframework.boot.batch.jdbc.autoconfigure;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.boot.batch.autoconfigure;
|
package org.springframework.boot.batch.jdbc.autoconfigure;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ public class BatchDataSourceScriptDatabaseInitializer extends DataSourceScriptDa
|
||||||
* @param properties the Spring Batch JDBC properties
|
* @param properties the Spring Batch JDBC properties
|
||||||
* @see #getSettings
|
* @see #getSettings
|
||||||
*/
|
*/
|
||||||
public BatchDataSourceScriptDatabaseInitializer(DataSource dataSource, BatchProperties.Jdbc properties) {
|
public BatchDataSourceScriptDatabaseInitializer(DataSource dataSource, BatchJdbcProperties properties) {
|
||||||
this(dataSource, getSettings(dataSource, properties));
|
this(dataSource, getSettings(dataSource, properties));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,16 +58,15 @@ public class BatchDataSourceScriptDatabaseInitializer extends DataSourceScriptDa
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapts {@link BatchProperties.Jdbc Spring Batch JDBC properties} to
|
* Adapts {@link BatchJdbcProperties} to {@link DatabaseInitializationSettings}
|
||||||
* {@link DatabaseInitializationSettings} replacing any {@literal @@platform@@}
|
* replacing any {@literal @@platform@@} placeholders.
|
||||||
* placeholders.
|
|
||||||
* @param dataSource the Spring Batch data source
|
* @param dataSource the Spring Batch data source
|
||||||
* @param properties batch JDBC properties
|
* @param properties batch JDBC properties
|
||||||
* @return a new {@link DatabaseInitializationSettings} instance
|
* @return a new {@link DatabaseInitializationSettings} instance
|
||||||
* @see #BatchDataSourceScriptDatabaseInitializer(DataSource,
|
* @see #BatchDataSourceScriptDatabaseInitializer(DataSource,
|
||||||
* DatabaseInitializationSettings)
|
* DatabaseInitializationSettings)
|
||||||
*/
|
*/
|
||||||
public static DatabaseInitializationSettings getSettings(DataSource dataSource, BatchProperties.Jdbc properties) {
|
public static DatabaseInitializationSettings getSettings(DataSource dataSource, BatchJdbcProperties properties) {
|
||||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||||
settings.setSchemaLocations(resolveSchemaLocations(dataSource, properties));
|
settings.setSchemaLocations(resolveSchemaLocations(dataSource, properties));
|
||||||
settings.setMode(properties.getInitializeSchema());
|
settings.setMode(properties.getInitializeSchema());
|
||||||
|
@ -75,7 +74,7 @@ public class BatchDataSourceScriptDatabaseInitializer extends DataSourceScriptDa
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> resolveSchemaLocations(DataSource dataSource, BatchProperties.Jdbc properties) {
|
private static List<String> resolveSchemaLocations(DataSource dataSource, BatchJdbcProperties properties) {
|
||||||
PlatformPlaceholderDatabaseDriverResolver platformResolver = new PlatformPlaceholderDatabaseDriverResolver();
|
PlatformPlaceholderDatabaseDriverResolver platformResolver = new PlatformPlaceholderDatabaseDriverResolver();
|
||||||
if (StringUtils.hasText(properties.getPlatform())) {
|
if (StringUtils.hasText(properties.getPlatform())) {
|
||||||
return platformResolver.resolveAll(properties.getPlatform(), properties.getSchema());
|
return platformResolver.resolveAll(properties.getPlatform(), properties.getSchema());
|
|
@ -0,0 +1,192 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.batch.jdbc.autoconfigure;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
||||||
|
import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration;
|
||||||
|
import org.springframework.batch.core.configuration.support.JdbcDefaultBatchConfiguration;
|
||||||
|
import org.springframework.batch.core.converter.JobParametersConverter;
|
||||||
|
import org.springframework.batch.core.launch.JobOperator;
|
||||||
|
import org.springframework.batch.core.repository.ExecutionContextSerializer;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.batch.autoconfigure.BatchAutoConfiguration;
|
||||||
|
import org.springframework.boot.batch.autoconfigure.BatchConversionServiceCustomizer;
|
||||||
|
import org.springframework.boot.batch.autoconfigure.BatchJobLauncherAutoConfiguration;
|
||||||
|
import org.springframework.boot.batch.autoconfigure.BatchTaskExecutor;
|
||||||
|
import org.springframework.boot.batch.autoconfigure.BatchTransactionManager;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
|
||||||
|
import org.springframework.boot.sql.autoconfigure.init.OnDatabaseInitializationCondition;
|
||||||
|
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer;
|
||||||
|
import org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Conditional;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||||
|
import org.springframework.core.task.TaskExecutor;
|
||||||
|
import org.springframework.jdbc.datasource.init.DatabasePopulator;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
import org.springframework.transaction.annotation.Isolation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link EnableAutoConfiguration Auto-configuration} for Spring Batch using a JDBC store.
|
||||||
|
*
|
||||||
|
* @author Dave Syer
|
||||||
|
* @author Eddú Meléndez
|
||||||
|
* @author Kazuki Shimizu
|
||||||
|
* @author Mahmoud Ben Hassine
|
||||||
|
* @author Lars Uffmann
|
||||||
|
* @author Lasse Wulff
|
||||||
|
* @author Yanming Zhou
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
@AutoConfiguration(before = { BatchAutoConfiguration.class, BatchJobLauncherAutoConfiguration.class },
|
||||||
|
after = { DataSourceAutoConfiguration.class, TransactionAutoConfiguration.class },
|
||||||
|
afterName = "org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration")
|
||||||
|
@ConditionalOnClass({ JobOperator.class, DataSource.class, DatabasePopulator.class })
|
||||||
|
@ConditionalOnBean({ DataSource.class, PlatformTransactionManager.class })
|
||||||
|
@ConditionalOnMissingBean(value = DefaultBatchConfiguration.class, annotation = EnableBatchProcessing.class)
|
||||||
|
@EnableConfigurationProperties(BatchJdbcProperties.class)
|
||||||
|
@Import(DatabaseInitializationDependencyConfigurer.class)
|
||||||
|
public final class BatchJdbcAutoConfiguration {
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class SpringBootBatchJdbcConfiguration extends JdbcDefaultBatchConfiguration {
|
||||||
|
|
||||||
|
private final DataSource dataSource;
|
||||||
|
|
||||||
|
private final PlatformTransactionManager transactionManager;
|
||||||
|
|
||||||
|
private final @Nullable TaskExecutor taskExecutor;
|
||||||
|
|
||||||
|
private final BatchJdbcProperties properties;
|
||||||
|
|
||||||
|
private final List<BatchConversionServiceCustomizer> batchConversionServiceCustomizers;
|
||||||
|
|
||||||
|
private final @Nullable ExecutionContextSerializer executionContextSerializer;
|
||||||
|
|
||||||
|
private final @Nullable JobParametersConverter jobParametersConverter;
|
||||||
|
|
||||||
|
SpringBootBatchJdbcConfiguration(DataSource dataSource,
|
||||||
|
@BatchDataSource ObjectProvider<DataSource> batchDataSource,
|
||||||
|
PlatformTransactionManager transactionManager,
|
||||||
|
@BatchTransactionManager ObjectProvider<PlatformTransactionManager> batchTransactionManager,
|
||||||
|
@BatchTaskExecutor ObjectProvider<TaskExecutor> batchTaskExecutor, BatchJdbcProperties properties,
|
||||||
|
ObjectProvider<BatchConversionServiceCustomizer> batchConversionServiceCustomizers,
|
||||||
|
ObjectProvider<ExecutionContextSerializer> executionContextSerializer,
|
||||||
|
ObjectProvider<JobParametersConverter> jobParametersConverter) {
|
||||||
|
this.dataSource = batchDataSource.getIfAvailable(() -> dataSource);
|
||||||
|
this.transactionManager = batchTransactionManager.getIfAvailable(() -> transactionManager);
|
||||||
|
this.taskExecutor = batchTaskExecutor.getIfAvailable();
|
||||||
|
this.properties = properties;
|
||||||
|
this.batchConversionServiceCustomizers = batchConversionServiceCustomizers.orderedStream().toList();
|
||||||
|
this.executionContextSerializer = executionContextSerializer.getIfAvailable();
|
||||||
|
this.jobParametersConverter = jobParametersConverter.getIfAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DataSource getDataSource() {
|
||||||
|
return this.dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PlatformTransactionManager getTransactionManager() {
|
||||||
|
return this.transactionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getTablePrefix() {
|
||||||
|
String tablePrefix = this.properties.getTablePrefix();
|
||||||
|
return (tablePrefix != null) ? tablePrefix : super.getTablePrefix();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean getValidateTransactionState() {
|
||||||
|
return this.properties.isValidateTransactionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Isolation getIsolationLevelForCreate() {
|
||||||
|
Isolation isolation = this.properties.getIsolationLevelForCreate();
|
||||||
|
return (isolation != null) ? isolation : super.getIsolationLevelForCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ConfigurableConversionService getConversionService() {
|
||||||
|
ConfigurableConversionService conversionService = super.getConversionService();
|
||||||
|
for (BatchConversionServiceCustomizer customizer : this.batchConversionServiceCustomizers) {
|
||||||
|
customizer.customize(conversionService);
|
||||||
|
}
|
||||||
|
return conversionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ExecutionContextSerializer getExecutionContextSerializer() {
|
||||||
|
return (this.executionContextSerializer != null) ? this.executionContextSerializer
|
||||||
|
: super.getExecutionContextSerializer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated(since = "4.0.0", forRemoval = true)
|
||||||
|
@SuppressWarnings("removal")
|
||||||
|
protected JobParametersConverter getJobParametersConverter() {
|
||||||
|
return (this.jobParametersConverter != null) ? this.jobParametersConverter
|
||||||
|
: super.getJobParametersConverter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TaskExecutor getTaskExecutor() {
|
||||||
|
return (this.taskExecutor != null) ? this.taskExecutor : super.getTaskExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@Conditional(OnBatchDatasourceInitializationCondition.class)
|
||||||
|
static class DataSourceInitializerConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
BatchDataSourceScriptDatabaseInitializer batchDataSourceInitializer(DataSource dataSource,
|
||||||
|
@BatchDataSource ObjectProvider<DataSource> batchDataSource, BatchJdbcProperties properties) {
|
||||||
|
return new BatchDataSourceScriptDatabaseInitializer(batchDataSource.getIfAvailable(() -> dataSource),
|
||||||
|
properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class OnBatchDatasourceInitializationCondition extends OnDatabaseInitializationCondition {
|
||||||
|
|
||||||
|
OnBatchDatasourceInitializationCondition() {
|
||||||
|
super("Batch", "spring.batch.jdbc.initialize-schema");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.batch.jdbc.autoconfigure;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.sql.init.DatabaseInitializationMode;
|
||||||
|
import org.springframework.transaction.annotation.Isolation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration properties for Spring Batch using a JDBC store.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
@ConfigurationProperties("spring.batch.jdbc")
|
||||||
|
public class BatchJdbcProperties {
|
||||||
|
|
||||||
|
private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/springframework/"
|
||||||
|
+ "batch/core/schema-@@platform@@.sql";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to validate the transaction state.
|
||||||
|
*/
|
||||||
|
private boolean validateTransactionState = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction isolation level to use when creating job meta-data for new jobs.
|
||||||
|
*/
|
||||||
|
private @Nullable Isolation isolationLevelForCreate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path to the SQL file to use to initialize the database schema.
|
||||||
|
*/
|
||||||
|
private String schema = DEFAULT_SCHEMA_LOCATION;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform to use in initialization scripts if the @@platform@@ placeholder is used.
|
||||||
|
* Auto-detected by default.
|
||||||
|
*/
|
||||||
|
private @Nullable String platform;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table prefix for all the batch meta-data tables.
|
||||||
|
*/
|
||||||
|
private @Nullable String tablePrefix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database schema initialization mode.
|
||||||
|
*/
|
||||||
|
private DatabaseInitializationMode initializeSchema = DatabaseInitializationMode.EMBEDDED;
|
||||||
|
|
||||||
|
public boolean isValidateTransactionState() {
|
||||||
|
return this.validateTransactionState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValidateTransactionState(boolean validateTransactionState) {
|
||||||
|
this.validateTransactionState = validateTransactionState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Isolation getIsolationLevelForCreate() {
|
||||||
|
return this.isolationLevelForCreate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsolationLevelForCreate(@Nullable Isolation isolationLevelForCreate) {
|
||||||
|
this.isolationLevelForCreate = isolationLevelForCreate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSchema() {
|
||||||
|
return this.schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSchema(String schema) {
|
||||||
|
this.schema = schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getPlatform() {
|
||||||
|
return this.platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlatform(@Nullable String platform) {
|
||||||
|
this.platform = platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getTablePrefix() {
|
||||||
|
return this.tablePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTablePrefix(@Nullable String tablePrefix) {
|
||||||
|
this.tablePrefix = tablePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DatabaseInitializationMode getInitializeSchema() {
|
||||||
|
return this.initializeSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInitializeSchema(DatabaseInitializationMode initializeSchema) {
|
||||||
|
this.initializeSchema = initializeSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.boot.batch.autoconfigure;
|
package org.springframework.boot.batch.jdbc.autoconfigure;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-configuration for Spring Batch JDBC.
|
||||||
|
*/
|
||||||
|
@NullMarked
|
||||||
|
package org.springframework.boot.batch.jdbc.autoconfigure;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"name": "spring.batch.initialize-schema",
|
||||||
|
"type": "org.springframework.boot.sql.init.DatabaseInitializationMode",
|
||||||
|
"deprecation": {
|
||||||
|
"replacement": "spring.batch.jdbc.initialize-schema",
|
||||||
|
"level": "error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "spring.batch.initializer.enabled",
|
||||||
|
"type": "java.lang.Boolean",
|
||||||
|
"description": "Create the required batch tables on startup if necessary. Enabled automatically\n if no custom table prefix is set or if a custom schema is configured.",
|
||||||
|
"deprecation": {
|
||||||
|
"replacement": "spring.batch.jdbc.initialize-schema",
|
||||||
|
"level": "error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "spring.batch.schema",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"deprecation": {
|
||||||
|
"replacement": "spring.batch.jdbc.schema",
|
||||||
|
"level": "error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "spring.batch.table-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"deprecation": {
|
||||||
|
"replacement": "spring.batch.jdbc.table-prefix",
|
||||||
|
"level": "error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
# Depends on Database Initialization Detectors
|
# Depends on Database Initialization Detectors
|
||||||
org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\
|
org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\
|
||||||
org.springframework.boot.batch.autoconfigure.JobRepositoryDependsOnDatabaseInitializationDetector
|
org.springframework.boot.batch.jdbc.autoconfigure.JobRepositoryDependsOnDatabaseInitializationDetector
|
|
@ -0,0 +1 @@
|
||||||
|
org.springframework.boot.batch.jdbc.autoconfigure.BatchJdbcAutoConfiguration
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.boot.batch.autoconfigure;
|
package org.springframework.boot.batch.jdbc.autoconfigure;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
|
@ -52,10 +52,10 @@ class BatchDataSourceScriptDatabaseInitializerTests {
|
||||||
@Test
|
@Test
|
||||||
void getSettingsWithPlatformDoesNotTouchDataSource() {
|
void getSettingsWithPlatformDoesNotTouchDataSource() {
|
||||||
DataSource dataSource = mock(DataSource.class);
|
DataSource dataSource = mock(DataSource.class);
|
||||||
BatchProperties properties = new BatchProperties();
|
BatchJdbcProperties properties = new BatchJdbcProperties();
|
||||||
properties.getJdbc().setPlatform("test");
|
properties.setPlatform("test");
|
||||||
DatabaseInitializationSettings settings = BatchDataSourceScriptDatabaseInitializer.getSettings(dataSource,
|
DatabaseInitializationSettings settings = BatchDataSourceScriptDatabaseInitializer.getSettings(dataSource,
|
||||||
properties.getJdbc());
|
properties);
|
||||||
assertThat(settings.getSchemaLocations())
|
assertThat(settings.getSchemaLocations())
|
||||||
.containsOnly("classpath:org/springframework/batch/core/schema-test.sql");
|
.containsOnly("classpath:org/springframework/batch/core/schema-test.sql");
|
||||||
then(dataSource).shouldHaveNoInteractions();
|
then(dataSource).shouldHaveNoInteractions();
|
||||||
|
@ -66,7 +66,7 @@ class BatchDataSourceScriptDatabaseInitializerTests {
|
||||||
"INFORMIX", "JTDS", "PHOENIX", "REDSHIFT", "TERADATA", "TESTCONTAINERS", "UNKNOWN" })
|
"INFORMIX", "JTDS", "PHOENIX", "REDSHIFT", "TERADATA", "TESTCONTAINERS", "UNKNOWN" })
|
||||||
void batchSchemaCanBeLocated(DatabaseDriver driver) throws SQLException {
|
void batchSchemaCanBeLocated(DatabaseDriver driver) throws SQLException {
|
||||||
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
|
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||||
BatchProperties properties = new BatchProperties();
|
BatchJdbcProperties properties = new BatchJdbcProperties();
|
||||||
DataSource dataSource = mock(DataSource.class);
|
DataSource dataSource = mock(DataSource.class);
|
||||||
Connection connection = mock(Connection.class);
|
Connection connection = mock(Connection.class);
|
||||||
given(dataSource.getConnection()).willReturn(connection);
|
given(dataSource.getConnection()).willReturn(connection);
|
||||||
|
@ -75,7 +75,7 @@ class BatchDataSourceScriptDatabaseInitializerTests {
|
||||||
String productName = (String) ReflectionTestUtils.getField(driver, "productName");
|
String productName = (String) ReflectionTestUtils.getField(driver, "productName");
|
||||||
given(metadata.getDatabaseProductName()).willReturn(productName);
|
given(metadata.getDatabaseProductName()).willReturn(productName);
|
||||||
DatabaseInitializationSettings settings = BatchDataSourceScriptDatabaseInitializer.getSettings(dataSource,
|
DatabaseInitializationSettings settings = BatchDataSourceScriptDatabaseInitializer.getSettings(dataSource,
|
||||||
properties.getJdbc());
|
properties);
|
||||||
List<String> schemaLocations = settings.getSchemaLocations();
|
List<String> schemaLocations = settings.getSchemaLocations();
|
||||||
assertThat(schemaLocations).isNotEmpty()
|
assertThat(schemaLocations).isNotEmpty()
|
||||||
.allSatisfy((location) -> assertThat(resourceLoader.getResource(location).exists()).isTrue());
|
.allSatisfy((location) -> assertThat(resourceLoader.getResource(location).exists()).isTrue());
|
|
@ -0,0 +1,822 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.batch.jdbc.autoconfigure;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManagerFactory;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import org.springframework.batch.core.BatchStatus;
|
||||||
|
import org.springframework.batch.core.configuration.JobRegistry;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
||||||
|
import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration;
|
||||||
|
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
|
||||||
|
import org.springframework.batch.core.converter.JobParametersConverter;
|
||||||
|
import org.springframework.batch.core.converter.JsonJobParametersConverter;
|
||||||
|
import org.springframework.batch.core.job.AbstractJob;
|
||||||
|
import org.springframework.batch.core.job.Job;
|
||||||
|
import org.springframework.batch.core.job.JobExecution;
|
||||||
|
import org.springframework.batch.core.job.parameters.JobParameters;
|
||||||
|
import org.springframework.batch.core.job.parameters.JobParametersBuilder;
|
||||||
|
import org.springframework.batch.core.launch.JobOperator;
|
||||||
|
import org.springframework.batch.core.repository.ExecutionContextSerializer;
|
||||||
|
import org.springframework.batch.core.repository.JobRepository;
|
||||||
|
import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer;
|
||||||
|
import org.springframework.batch.core.repository.dao.Jackson2ExecutionContextStringSerializer;
|
||||||
|
import org.springframework.batch.core.step.Step;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.boot.DefaultApplicationArguments;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
|
||||||
|
import org.springframework.boot.batch.autoconfigure.BatchConversionServiceCustomizer;
|
||||||
|
import org.springframework.boot.batch.autoconfigure.BatchJobLauncherAutoConfiguration;
|
||||||
|
import org.springframework.boot.batch.autoconfigure.BatchTaskExecutor;
|
||||||
|
import org.springframework.boot.batch.autoconfigure.BatchTransactionManager;
|
||||||
|
import org.springframework.boot.batch.autoconfigure.JobLauncherApplicationRunner;
|
||||||
|
import org.springframework.boot.batch.jdbc.autoconfigure.BatchJdbcAutoConfiguration.SpringBootBatchJdbcConfiguration;
|
||||||
|
import org.springframework.boot.batch.jdbc.autoconfigure.domain.City;
|
||||||
|
import org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration;
|
||||||
|
import org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration;
|
||||||
|
import org.springframework.boot.jdbc.DataSourceBuilder;
|
||||||
|
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
|
||||||
|
import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
|
||||||
|
import org.springframework.boot.jdbc.autoconfigure.EmbeddedDataSourceConfiguration;
|
||||||
|
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
|
||||||
|
import org.springframework.boot.liquibase.autoconfigure.LiquibaseAutoConfiguration;
|
||||||
|
import org.springframework.boot.sql.init.DatabaseInitializationMode;
|
||||||
|
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
|
||||||
|
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||||
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
|
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources;
|
||||||
|
import org.springframework.boot.testsupport.classpath.resources.WithResource;
|
||||||
|
import org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration;
|
||||||
|
import org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizationAutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||||
|
import org.springframework.core.task.AsyncTaskExecutor;
|
||||||
|
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||||
|
import org.springframework.core.task.SyncTaskExecutor;
|
||||||
|
import org.springframework.core.task.TaskExecutor;
|
||||||
|
import org.springframework.jdbc.BadSqlGrammarException;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||||
|
import org.springframework.jdbc.datasource.init.DatabasePopulator;
|
||||||
|
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||||
|
import org.springframework.test.util.AopTestUtils;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
import org.springframework.transaction.annotation.Isolation;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link BatchJdbcAutoConfiguration}.
|
||||||
|
*
|
||||||
|
* @author Dave Syer
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @author Vedran Pavic
|
||||||
|
* @author Kazuki Shimizu
|
||||||
|
* @author Mahmoud Ben Hassine
|
||||||
|
* @author Lars Uffmann
|
||||||
|
* @author Lasse Wulff
|
||||||
|
* @author Yanming Zhou
|
||||||
|
*/
|
||||||
|
class BatchJdbcAutoConfigurationTests {
|
||||||
|
|
||||||
|
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||||
|
.withConfiguration(AutoConfigurations.of(BatchJobLauncherAutoConfiguration.class,
|
||||||
|
BatchJdbcAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class,
|
||||||
|
TransactionAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class));
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDefaultContext() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobRepository.class);
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
assertThat(context.getBean(BatchJdbcProperties.class).getInitializeSchema())
|
||||||
|
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
|
||||||
|
assertThat(new JdbcTemplate(context.getBean(DataSource.class))
|
||||||
|
.queryForList("select * from BATCH_JOB_EXECUTION")).isEmpty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void autoconfigurationBacksOffEntirelyIfSpringJdbcAbsent() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
|
||||||
|
.withClassLoader(new FilteredClassLoader(DatabasePopulator.class))
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).doesNotHaveBean(JobLauncherApplicationRunner.class);
|
||||||
|
assertThat(context).doesNotHaveBean(BatchDataSourceScriptDatabaseInitializer.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void autoConfigurationBacksOffWhenUserEnablesBatchProcessing() {
|
||||||
|
this.contextRunner
|
||||||
|
.withUserConfiguration(EnableBatchProcessingConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||||
|
.withClassLoader(new FilteredClassLoader(DatabasePopulator.class))
|
||||||
|
.run((context) -> assertThat(context).doesNotHaveBean(BatchJdbcAutoConfiguration.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void autoConfigurationBacksOffWhenUserProvidesBatchConfiguration() {
|
||||||
|
this.contextRunner.withUserConfiguration(CustomBatchConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||||
|
.withClassLoader(new FilteredClassLoader(DatabasePopulator.class))
|
||||||
|
.run((context) -> assertThat(context).doesNotHaveBean(BatchJdbcAutoConfiguration.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDefinesAndLaunchesJob() {
|
||||||
|
this.contextRunner.withUserConfiguration(JobConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
context.getBean(JobLauncherApplicationRunner.class)
|
||||||
|
.run(new DefaultApplicationArguments("jobParam=test"));
|
||||||
|
JobParameters jobParameters = new JobParametersBuilder().addString("jobParam", "test")
|
||||||
|
.toJobParameters();
|
||||||
|
assertThat(context.getBean(JobRepository.class).getLastJobExecution("job", jobParameters)).isNotNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDefinesAndLaunchesJobIgnoreOptionArguments() {
|
||||||
|
this.contextRunner.withUserConfiguration(JobConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
context.getBean(JobLauncherApplicationRunner.class)
|
||||||
|
.run(new DefaultApplicationArguments("--spring.property=value", "jobParam=test"));
|
||||||
|
JobParameters jobParameters = new JobParametersBuilder().addString("jobParam", "test")
|
||||||
|
.toJobParameters();
|
||||||
|
assertThat(context.getBean(JobRepository.class).getLastJobExecution("job", jobParameters)).isNotNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRegisteredAndLocalJob() {
|
||||||
|
this.contextRunner
|
||||||
|
.withUserConfiguration(NamedJobConfigurationWithRegisteredAndLocalJob.class,
|
||||||
|
EmbeddedDataSourceConfiguration.class)
|
||||||
|
.withPropertyValues("spring.batch.job.name:discreteRegisteredJob")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
context.getBean(JobLauncherApplicationRunner.class).run();
|
||||||
|
assertThat(context.getBean(JobRepository.class)
|
||||||
|
.getLastJobExecution("discreteRegisteredJob", new JobParameters())
|
||||||
|
.getStatus()).isEqualTo(BatchStatus.COMPLETED);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDefinesAndLaunchesLocalJob() {
|
||||||
|
this.contextRunner
|
||||||
|
.withUserConfiguration(NamedJobConfigurationWithLocalJob.class, EmbeddedDataSourceConfiguration.class)
|
||||||
|
.withPropertyValues("spring.batch.job.name:discreteLocalJob")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
context.getBean(JobLauncherApplicationRunner.class).run();
|
||||||
|
assertThat(context.getBean(JobRepository.class)
|
||||||
|
.getLastJobExecution("discreteLocalJob", new JobParameters())).isNotNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMultipleJobsAndNoJobName() {
|
||||||
|
this.contextRunner.withUserConfiguration(MultipleJobConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasFailed();
|
||||||
|
assertThat(context.getStartupFailure().getCause().getMessage())
|
||||||
|
.contains("Job name must be specified in case of multiple jobs");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMultipleJobsAndJobName() {
|
||||||
|
this.contextRunner.withUserConfiguration(MultipleJobConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||||
|
.withPropertyValues("spring.batch.job.name:discreteLocalJob")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
context.getBean(JobLauncherApplicationRunner.class).run();
|
||||||
|
assertThat(context.getBean(JobRepository.class)
|
||||||
|
.getLastJobExecution("discreteLocalJob", new JobParameters())).isNotNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDisableLaunchesJob() {
|
||||||
|
this.contextRunner.withUserConfiguration(JobConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||||
|
.withPropertyValues("spring.batch.job.enabled:false")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
assertThat(context).doesNotHaveBean(CommandLineRunner.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDisableSchemaLoader() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
|
||||||
|
.withPropertyValues("spring.datasource.generate-unique-name=true",
|
||||||
|
"spring.batch.jdbc.initialize-schema:never")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
assertThat(context.getBean(BatchJdbcProperties.class).getInitializeSchema())
|
||||||
|
.isEqualTo(DatabaseInitializationMode.NEVER);
|
||||||
|
assertThat(context).doesNotHaveBean(BatchDataSourceScriptDatabaseInitializer.class);
|
||||||
|
assertThatExceptionOfType(BadSqlGrammarException.class)
|
||||||
|
.isThrownBy(() -> new JdbcTemplate(context.getBean(DataSource.class))
|
||||||
|
.queryForList("select * from BATCH_JOB_EXECUTION"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUsingJpa() {
|
||||||
|
this.contextRunner
|
||||||
|
.withUserConfiguration(TestJpaConfiguration.class, EmbeddedDataSourceConfiguration.class,
|
||||||
|
HibernateJpaAutoConfiguration.class)
|
||||||
|
.run((context) -> {
|
||||||
|
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
|
||||||
|
// It's a lazy proxy, but it does render its target if you ask for
|
||||||
|
// toString():
|
||||||
|
assertThat(transactionManager.toString()).contains("JpaTransactionManager");
|
||||||
|
assertThat(context).hasSingleBean(EntityManagerFactory.class);
|
||||||
|
// Ensure the JobRepository can be used (no problem with isolation
|
||||||
|
// level)
|
||||||
|
assertThat(context.getBean(JobRepository.class).getLastJobExecution("job", new JobParameters()))
|
||||||
|
.isNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithPackageResources("custom-schema.sql")
|
||||||
|
void testRenamePrefix() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
|
||||||
|
.withPropertyValues("spring.datasource.generate-unique-name=true",
|
||||||
|
"spring.batch.jdbc.schema:classpath:custom-schema.sql", "spring.batch.jdbc.table-prefix:PREFIX_")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
assertThat(context.getBean(BatchJdbcProperties.class).getInitializeSchema())
|
||||||
|
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
|
||||||
|
assertThat(new JdbcTemplate(context.getBean(DataSource.class))
|
||||||
|
.queryForList("select * from PREFIX_JOB_EXECUTION")).isEmpty();
|
||||||
|
JobRepository jobRepository = context.getBean(JobRepository.class);
|
||||||
|
assertThat(jobRepository.findRunningJobExecutions("test")).isEmpty();
|
||||||
|
assertThat(jobRepository.getLastJobExecution("test", new JobParameters())).isNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCustomizeJpaTransactionManagerUsingProperties() {
|
||||||
|
this.contextRunner
|
||||||
|
.withUserConfiguration(TestJpaConfiguration.class, EmbeddedDataSourceConfiguration.class,
|
||||||
|
HibernateJpaAutoConfiguration.class)
|
||||||
|
.withPropertyValues("spring.transaction.default-timeout:30",
|
||||||
|
"spring.transaction.rollback-on-commit-failure:true")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(BatchJobLauncherAutoConfiguration.class);
|
||||||
|
JpaTransactionManager transactionManager = JpaTransactionManager.class
|
||||||
|
.cast(context.getBean(SpringBootBatchJdbcConfiguration.class).getTransactionManager());
|
||||||
|
assertThat(transactionManager.getDefaultTimeout()).isEqualTo(30);
|
||||||
|
assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCustomizeDataSourceTransactionManagerUsingProperties() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
|
||||||
|
.withPropertyValues("spring.transaction.default-timeout:30",
|
||||||
|
"spring.transaction.rollback-on-commit-failure:true")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(BatchJdbcAutoConfiguration.class);
|
||||||
|
DataSourceTransactionManager transactionManager = DataSourceTransactionManager.class
|
||||||
|
.cast(context.getBean(SpringBootBatchJdbcConfiguration.class).getTransactionManager());
|
||||||
|
assertThat(transactionManager.getDefaultTimeout()).isEqualTo(30);
|
||||||
|
assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBatchDataSource() {
|
||||||
|
this.contextRunner.withUserConfiguration(BatchDataSourceConfiguration.class).run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(BatchJdbcAutoConfiguration.class)
|
||||||
|
.hasSingleBean(BatchDataSourceScriptDatabaseInitializer.class)
|
||||||
|
.hasBean("batchDataSource");
|
||||||
|
DataSource batchDataSource = context.getBean("batchDataSource", DataSource.class);
|
||||||
|
assertThat(context.getBean(SpringBootBatchJdbcConfiguration.class).getDataSource())
|
||||||
|
.isEqualTo(batchDataSource);
|
||||||
|
assertThat(context.getBean(BatchDataSourceScriptDatabaseInitializer.class))
|
||||||
|
.hasFieldOrPropertyWithValue("dataSource", batchDataSource);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBatchTransactionManager() {
|
||||||
|
this.contextRunner.withUserConfiguration(BatchTransactionManagerConfiguration.class).run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(BatchJdbcAutoConfiguration.class);
|
||||||
|
PlatformTransactionManager batchTransactionManager = context.getBean("batchTransactionManager",
|
||||||
|
PlatformTransactionManager.class);
|
||||||
|
assertThat(context.getBean(SpringBootBatchJdbcConfiguration.class).getTransactionManager())
|
||||||
|
.isEqualTo(batchTransactionManager);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBatchTaskExecutor() {
|
||||||
|
this.contextRunner
|
||||||
|
.withUserConfiguration(BatchTaskExecutorConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(BatchJdbcAutoConfiguration.class).hasBean("batchTaskExecutor");
|
||||||
|
TaskExecutor batchTaskExecutor = context.getBean("batchTaskExecutor", TaskExecutor.class);
|
||||||
|
assertThat(batchTaskExecutor).isInstanceOf(AsyncTaskExecutor.class);
|
||||||
|
assertThat(context.getBean(SpringBootBatchJdbcConfiguration.class).getTaskExecutor())
|
||||||
|
.isEqualTo(batchTaskExecutor);
|
||||||
|
JobOperator jobOperator = AopTestUtils.getTargetObject(context.getBean(JobOperator.class));
|
||||||
|
assertThat(jobOperator).hasFieldOrPropertyWithValue("taskExecutor", batchTaskExecutor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void jobRepositoryBeansDependOnBatchDataSourceInitializer() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
|
||||||
|
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
|
||||||
|
String[] jobRepositoryNames = beanFactory.getBeanNamesForType(JobRepository.class);
|
||||||
|
assertThat(jobRepositoryNames).isNotEmpty();
|
||||||
|
for (String jobRepositoryName : jobRepositoryNames) {
|
||||||
|
assertThat(beanFactory.getBeanDefinition(jobRepositoryName).getDependsOn())
|
||||||
|
.contains("batchDataSourceInitializer");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void jobRepositoryBeansDependOnFlyway() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, FlywayAutoConfiguration.class)
|
||||||
|
.withPropertyValues("spring.batch.jdbc.initialize-schema=never")
|
||||||
|
.run((context) -> {
|
||||||
|
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
|
||||||
|
String[] jobRepositoryNames = beanFactory.getBeanNamesForType(JobRepository.class);
|
||||||
|
assertThat(jobRepositoryNames).isNotEmpty();
|
||||||
|
for (String jobRepositoryName : jobRepositoryNames) {
|
||||||
|
assertThat(beanFactory.getBeanDefinition(jobRepositoryName).getDependsOn()).contains("flyway",
|
||||||
|
"flywayInitializer");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithResource(name = "db/changelog/db.changelog-master.yaml", content = "databaseChangeLog:")
|
||||||
|
void jobRepositoryBeansDependOnLiquibase() {
|
||||||
|
this.contextRunner
|
||||||
|
.withUserConfiguration(EmbeddedDataSourceConfiguration.class, LiquibaseAutoConfiguration.class)
|
||||||
|
.withPropertyValues("spring.batch.jdbc.initialize-schema=never")
|
||||||
|
.run((context) -> {
|
||||||
|
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
|
||||||
|
String[] jobRepositoryNames = beanFactory.getBeanNamesForType(JobRepository.class);
|
||||||
|
assertThat(jobRepositoryNames).isNotEmpty();
|
||||||
|
for (String jobRepositoryName : jobRepositoryNames) {
|
||||||
|
assertThat(beanFactory.getBeanDefinition(jobRepositoryName).getDependsOn()).contains("liquibase");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenTheUserDefinesTheirOwnBatchDatabaseInitializerThenTheAutoConfiguredInitializerBacksOff() {
|
||||||
|
this.contextRunner.withUserConfiguration(CustomBatchDatabaseInitializerConfiguration.class)
|
||||||
|
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
|
||||||
|
DataSourceTransactionManagerAutoConfiguration.class))
|
||||||
|
.run((context) -> assertThat(context).hasSingleBean(BatchDataSourceScriptDatabaseInitializer.class)
|
||||||
|
.doesNotHaveBean("batchDataSourceScriptDatabaseInitializer")
|
||||||
|
.hasBean("customInitializer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenTheUserDefinesTheirOwnDatabaseInitializerThenTheAutoConfiguredBatchInitializerRemains() {
|
||||||
|
this.contextRunner.withUserConfiguration(CustomDatabaseInitializerConfiguration.class)
|
||||||
|
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
|
||||||
|
DataSourceTransactionManagerAutoConfiguration.class))
|
||||||
|
.run((context) -> assertThat(context).hasSingleBean(BatchDataSourceScriptDatabaseInitializer.class)
|
||||||
|
.hasBean("customInitializer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void conversionServiceCustomizersAreCalled() {
|
||||||
|
this.contextRunner
|
||||||
|
.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
|
||||||
|
ConversionServiceCustomizersConfiguration.class)
|
||||||
|
.run((context) -> {
|
||||||
|
BatchConversionServiceCustomizer customizer = context.getBean("batchConversionServiceCustomizer",
|
||||||
|
BatchConversionServiceCustomizer.class);
|
||||||
|
BatchConversionServiceCustomizer anotherCustomizer = context
|
||||||
|
.getBean("anotherBatchConversionServiceCustomizer", BatchConversionServiceCustomizer.class);
|
||||||
|
InOrder inOrder = Mockito.inOrder(customizer, anotherCustomizer);
|
||||||
|
ConfigurableConversionService configurableConversionService = context
|
||||||
|
.getBean(SpringBootBatchJdbcConfiguration.class)
|
||||||
|
.getConversionService();
|
||||||
|
inOrder.verify(customizer).customize(configurableConversionService);
|
||||||
|
inOrder.verify(anotherCustomizer).customize(configurableConversionService);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenTheUserDefinesAJobNameAsJobInstanceValidates() {
|
||||||
|
JobLauncherApplicationRunner runner = createInstance("another");
|
||||||
|
runner.setJobs(Collections.singletonList(mockJob("test")));
|
||||||
|
runner.setJobName("test");
|
||||||
|
runner.afterPropertiesSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenTheUserDefinesAJobNameAsRegisteredJobValidates() {
|
||||||
|
JobLauncherApplicationRunner runner = createInstance("test");
|
||||||
|
runner.setJobName("test");
|
||||||
|
runner.afterPropertiesSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenTheUserDefinesAJobNameThatDoesNotExistWithJobInstancesFailsFast() {
|
||||||
|
JobLauncherApplicationRunner runner = createInstance();
|
||||||
|
runner.setJobs(Arrays.asList(mockJob("one"), mockJob("two")));
|
||||||
|
runner.setJobName("three");
|
||||||
|
assertThatIllegalStateException().isThrownBy(runner::afterPropertiesSet)
|
||||||
|
.withMessage("No job found with name 'three'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenTheUserDefinesAJobNameThatDoesNotExistWithRegisteredJobFailsFast() {
|
||||||
|
JobLauncherApplicationRunner runner = createInstance("one", "two");
|
||||||
|
runner.setJobName("three");
|
||||||
|
assertThatIllegalStateException().isThrownBy(runner::afterPropertiesSet)
|
||||||
|
.withMessage("No job found with name 'three'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void customExecutionContextSerializerIsUsed() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
|
||||||
|
.withBean(ExecutionContextSerializer.class, Jackson2ExecutionContextStringSerializer::new)
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(Jackson2ExecutionContextStringSerializer.class);
|
||||||
|
assertThat(context.getBean(SpringBootBatchJdbcConfiguration.class).getExecutionContextSerializer())
|
||||||
|
.isInstanceOf(Jackson2ExecutionContextStringSerializer.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void defaultExecutionContextSerializerIsUsed() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
|
||||||
|
assertThat(context).doesNotHaveBean(ExecutionContextSerializer.class);
|
||||||
|
assertThat(context.getBean(SpringBootBatchJdbcConfiguration.class).getExecutionContextSerializer())
|
||||||
|
.isInstanceOf(DefaultExecutionContextSerializer.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void customJdbcPropertiesIsUsed() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
|
||||||
|
.withPropertyValues("spring.batch.jdbc.validate-transaction-state:false",
|
||||||
|
"spring.batch.jdbc.isolation-level-for-create:READ_COMMITTED")
|
||||||
|
.run((context) -> {
|
||||||
|
SpringBootBatchJdbcConfiguration configuration = context
|
||||||
|
.getBean(SpringBootBatchJdbcConfiguration.class);
|
||||||
|
assertThat(configuration.getValidateTransactionState()).isEqualTo(false);
|
||||||
|
assertThat(configuration.getIsolationLevelForCreate()).isEqualTo(Isolation.READ_COMMITTED);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Deprecated(since = "4.0.0", forRemoval = true)
|
||||||
|
@SuppressWarnings("removal")
|
||||||
|
void customJobParametersConverterIsUsed() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
|
||||||
|
.withBean(JobParametersConverter.class, JsonJobParametersConverter::new)
|
||||||
|
.withPropertyValues("spring.datasource.generate-unique-name=true")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JsonJobParametersConverter.class);
|
||||||
|
assertThat(context.getBean(SpringBootBatchJdbcConfiguration.class).getJobParametersConverter())
|
||||||
|
.isInstanceOf(JsonJobParametersConverter.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Deprecated(since = "4.0.0", forRemoval = true)
|
||||||
|
@SuppressWarnings("removal")
|
||||||
|
void defaultJobParametersConverterIsUsed() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
|
||||||
|
assertThat(context).doesNotHaveBean(JobParametersConverter.class);
|
||||||
|
assertThat(context.getBean(SpringBootBatchJdbcConfiguration.class).getJobParametersConverter())
|
||||||
|
.isInstanceOf(DefaultJobParametersConverter.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private JobLauncherApplicationRunner createInstance(String... registeredJobNames) {
|
||||||
|
JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(mock(JobOperator.class));
|
||||||
|
JobRegistry jobRegistry = mock(JobRegistry.class);
|
||||||
|
given(jobRegistry.getJobNames()).willReturn(Arrays.asList(registeredJobNames));
|
||||||
|
runner.setJobRegistry(jobRegistry);
|
||||||
|
return runner;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Job mockJob(String name) {
|
||||||
|
Job job = mock(Job.class);
|
||||||
|
given(job.getName()).willReturn(name);
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class BatchDataSourceConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
DataSource normalDataSource() {
|
||||||
|
return DataSourceBuilder.create().url("jdbc:h2:mem:normal").username("sa").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BatchDataSource
|
||||||
|
@Bean(defaultCandidate = false)
|
||||||
|
DataSource batchDataSource() {
|
||||||
|
return DataSourceBuilder.create().url("jdbc:h2:mem:batchdatasource").username("sa").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class BatchTransactionManagerConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
DataSource dataSource() {
|
||||||
|
return DataSourceBuilder.create().url("jdbc:h2:mem:database").username("sa").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Primary
|
||||||
|
PlatformTransactionManager normalTransactionManager() {
|
||||||
|
return mock(PlatformTransactionManager.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BatchTransactionManager
|
||||||
|
@Bean(defaultCandidate = false)
|
||||||
|
PlatformTransactionManager batchTransactionManager() {
|
||||||
|
return mock(PlatformTransactionManager.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class BatchTaskExecutorConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
TaskExecutor taskExecutor() {
|
||||||
|
return new SyncTaskExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BatchTaskExecutor
|
||||||
|
@Bean(defaultCandidate = false)
|
||||||
|
TaskExecutor batchTaskExecutor() {
|
||||||
|
return new SimpleAsyncTaskExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class EmptyConfiguration {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestAutoConfigurationPackage(City.class)
|
||||||
|
static class TestJpaConfiguration {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class EntityManagerFactoryConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
EntityManagerFactory entityManagerFactory() {
|
||||||
|
return mock(EntityManagerFactory.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class NamedJobConfigurationWithRegisteredAndLocalJob {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobRepository jobRepository;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Job discreteJob() {
|
||||||
|
AbstractJob job = new AbstractJob("discreteRegisteredJob") {
|
||||||
|
|
||||||
|
private static int count = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getStepNames() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Step getStep(String stepName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doExecute(JobExecution execution) {
|
||||||
|
if (count == 0) {
|
||||||
|
execution.setStatus(BatchStatus.COMPLETED);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
execution.setStatus(BatchStatus.FAILED);
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
job.setJobRepository(this.jobRepository);
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class NamedJobConfigurationWithLocalJob {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobRepository jobRepository;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Job discreteJob() {
|
||||||
|
AbstractJob job = new AbstractJob("discreteLocalJob") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getStepNames() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Step getStep(String stepName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doExecute(JobExecution execution) {
|
||||||
|
execution.setStatus(BatchStatus.COMPLETED);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
job.setJobRepository(this.jobRepository);
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class MultipleJobConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobRepository jobRepository;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Job discreteJob() {
|
||||||
|
AbstractJob job = new AbstractJob("discreteLocalJob") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getStepNames() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Step getStep(String stepName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doExecute(JobExecution execution) {
|
||||||
|
execution.setStatus(BatchStatus.COMPLETED);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
job.setJobRepository(this.jobRepository);
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Job job2() {
|
||||||
|
return new Job() {
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "discreteLocalJob2";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(JobExecution execution) {
|
||||||
|
execution.setStatus(BatchStatus.COMPLETED);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class JobConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobRepository jobRepository;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Job job() {
|
||||||
|
AbstractJob job = new AbstractJob() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getStepNames() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Step getStep(String stepName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doExecute(JobExecution execution) {
|
||||||
|
execution.setStatus(BatchStatus.COMPLETED);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
job.setJobRepository(this.jobRepository);
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CustomBatchDatabaseInitializerConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
BatchDataSourceScriptDatabaseInitializer customInitializer(DataSource dataSource,
|
||||||
|
BatchJdbcProperties properties) {
|
||||||
|
return new BatchDataSourceScriptDatabaseInitializer(dataSource, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CustomDatabaseInitializerConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
DataSourceScriptDatabaseInitializer customInitializer(DataSource dataSource) {
|
||||||
|
return new DataSourceScriptDatabaseInitializer(dataSource, new DatabaseInitializationSettings());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CustomBatchConfiguration extends DefaultBatchConfiguration {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableBatchProcessing
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class EnableBatchProcessingConfiguration {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class ConversionServiceCustomizersConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(1)
|
||||||
|
BatchConversionServiceCustomizer batchConversionServiceCustomizer() {
|
||||||
|
return mock(BatchConversionServiceCustomizer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(2)
|
||||||
|
BatchConversionServiceCustomizer anotherBatchConversionServiceCustomizer() {
|
||||||
|
return mock(BatchConversionServiceCustomizer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.boot.batch.autoconfigure;
|
package org.springframework.boot.batch.jdbc.autoconfigure;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
@ -25,8 +25,8 @@ import org.springframework.batch.core.launch.JobOperator;
|
||||||
import org.springframework.batch.core.repository.JobRepository;
|
import org.springframework.batch.core.repository.JobRepository;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
|
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
|
||||||
import org.springframework.boot.batch.autoconfigure.BatchAutoConfiguration.SpringBootBatchConfiguration;
|
import org.springframework.boot.batch.jdbc.autoconfigure.BatchJdbcAutoConfiguration.SpringBootBatchJdbcConfiguration;
|
||||||
import org.springframework.boot.batch.autoconfigure.domain.City;
|
import org.springframework.boot.batch.jdbc.autoconfigure.domain.City;
|
||||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
|
import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
|
||||||
import org.springframework.boot.jdbc.autoconfigure.EmbeddedDataSourceConfiguration;
|
import org.springframework.boot.jdbc.autoconfigure.EmbeddedDataSourceConfiguration;
|
||||||
import org.springframework.boot.sql.init.DatabaseInitializationMode;
|
import org.springframework.boot.sql.init.DatabaseInitializationMode;
|
||||||
|
@ -40,15 +40,15 @@ import org.springframework.transaction.annotation.Isolation;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link BatchAutoConfiguration} when JPA is not on the classpath.
|
* Tests for {@link BatchJdbcAutoConfiguration} when JPA is not on the classpath.
|
||||||
*
|
*
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
*/
|
*/
|
||||||
@ClassPathExclusions("hibernate-jpa-*.jar")
|
@ClassPathExclusions("hibernate-jpa-*.jar")
|
||||||
class BatchAutoConfigurationWithoutJpaTests {
|
class BatchJdbcAutoConfigurationWithoutJpaTests {
|
||||||
|
|
||||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||||
.withConfiguration(AutoConfigurations.of(BatchAutoConfiguration.class, TransactionAutoConfiguration.class,
|
.withConfiguration(AutoConfigurations.of(BatchJdbcAutoConfiguration.class, TransactionAutoConfiguration.class,
|
||||||
DataSourceTransactionManagerAutoConfiguration.class));
|
DataSourceTransactionManagerAutoConfiguration.class));
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -58,7 +58,7 @@ class BatchAutoConfigurationWithoutJpaTests {
|
||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
assertThat(context).hasSingleBean(JobOperator.class);
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
assertThat(context).hasSingleBean(JobRepository.class);
|
assertThat(context).hasSingleBean(JobRepository.class);
|
||||||
assertThat(context.getBean(BatchProperties.class).getJdbc().getInitializeSchema())
|
assertThat(context.getBean(BatchJdbcProperties.class).getInitializeSchema())
|
||||||
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
|
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
|
||||||
assertThat(new JdbcTemplate(context.getBean(DataSource.class))
|
assertThat(new JdbcTemplate(context.getBean(DataSource.class))
|
||||||
.queryForList("select * from BATCH_JOB_EXECUTION")).isEmpty();
|
.queryForList("select * from BATCH_JOB_EXECUTION")).isEmpty();
|
||||||
|
@ -89,7 +89,7 @@ class BatchAutoConfigurationWithoutJpaTests {
|
||||||
.withPropertyValues("spring.datasource.generate-unique-name=true",
|
.withPropertyValues("spring.datasource.generate-unique-name=true",
|
||||||
"spring.batch.jdbc.isolation-level-for-create=read_committed")
|
"spring.batch.jdbc.isolation-level-for-create=read_committed")
|
||||||
.run((context) -> assertThat(
|
.run((context) -> assertThat(
|
||||||
context.getBean(SpringBootBatchConfiguration.class).getIsolationLevelForCreate())
|
context.getBean(SpringBootBatchJdbcConfiguration.class).getIsolationLevelForCreate())
|
||||||
.isEqualTo(Isolation.READ_COMMITTED));
|
.isEqualTo(Isolation.READ_COMMITTED));
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.boot.batch.autoconfigure;
|
package org.springframework.boot.batch.jdbc.autoconfigure;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@ -23,15 +23,15 @@ import org.springframework.batch.core.configuration.support.JdbcDefaultBatchConf
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link BatchProperties}.
|
* Tests for {@link BatchJdbcProperties}.
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
class BatchPropertiesTests {
|
class BatchJdbcPropertiesTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void validateTransactionStateDefaultMatchesSpringBatchDefault() {
|
void validateTransactionStateDefaultMatchesSpringBatchDefault() {
|
||||||
assertThat(new BatchProperties().getJdbc().isValidateTransactionState())
|
assertThat(new BatchJdbcProperties().isValidateTransactionState())
|
||||||
.isEqualTo(new TestBatchConfiguration().getValidateTransactionState());
|
.isEqualTo(new TestBatchConfiguration().getValidateTransactionState());
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.boot.batch.autoconfigure.domain;
|
package org.springframework.boot.batch.jdbc.autoconfigure.domain;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
|
@ -26,18 +26,12 @@ description = "Spring Boot Batch"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":core:spring-boot"))
|
api(project(":core:spring-boot"))
|
||||||
api(project(":module:spring-boot-jdbc"))
|
|
||||||
api("org.springframework.batch:spring-batch-core")
|
api("org.springframework.batch:spring-batch-core")
|
||||||
|
|
||||||
implementation(project(":module:spring-boot-tx"))
|
|
||||||
|
|
||||||
optional(project(":core:spring-boot-autoconfigure"))
|
optional(project(":core:spring-boot-autoconfigure"))
|
||||||
optional(project(":module:spring-boot-hibernate"))
|
|
||||||
optional(project(":module:spring-boot-micrometer-observation"))
|
optional(project(":module:spring-boot-micrometer-observation"))
|
||||||
|
|
||||||
testImplementation(project(":core:spring-boot-test"))
|
testImplementation(project(":core:spring-boot-test"))
|
||||||
testImplementation(project(":module:spring-boot-flyway"))
|
|
||||||
testImplementation(project(":module:spring-boot-liquibase"))
|
|
||||||
testImplementation(project(":test-support:spring-boot-test-support"))
|
testImplementation(project(":test-support:spring-boot-test-support"))
|
||||||
testImplementation(testFixtures(project(":core:spring-boot-autoconfigure")))
|
testImplementation(testFixtures(project(":core:spring-boot-autoconfigure")))
|
||||||
testImplementation("io.micrometer:micrometer-observation-test")
|
testImplementation("io.micrometer:micrometer-observation-test")
|
||||||
|
@ -45,6 +39,4 @@ dependencies {
|
||||||
testRuntimeOnly("ch.qos.logback:logback-classic")
|
testRuntimeOnly("ch.qos.logback:logback-classic")
|
||||||
testRuntimeOnly("com.fasterxml.jackson.core:jackson-databind")
|
testRuntimeOnly("com.fasterxml.jackson.core:jackson-databind")
|
||||||
testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
|
testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
|
||||||
testRuntimeOnly("com.h2database:h2")
|
|
||||||
testRuntimeOnly("com.zaxxer:HikariCP")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,163 +16,47 @@
|
||||||
|
|
||||||
package org.springframework.boot.batch.autoconfigure;
|
package org.springframework.boot.batch.autoconfigure;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
||||||
import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration;
|
import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration;
|
||||||
import org.springframework.batch.core.configuration.support.JdbcDefaultBatchConfiguration;
|
|
||||||
import org.springframework.batch.core.converter.JobParametersConverter;
|
import org.springframework.batch.core.converter.JobParametersConverter;
|
||||||
import org.springframework.batch.core.launch.JobOperator;
|
import org.springframework.batch.core.launch.JobOperator;
|
||||||
import org.springframework.batch.core.repository.ExecutionContextSerializer;
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.boot.ExitCodeGenerator;
|
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
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.ConditionalOnClass;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
|
|
||||||
import org.springframework.boot.sql.autoconfigure.init.OnDatabaseInitializationCondition;
|
|
||||||
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer;
|
|
||||||
import org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Conditional;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
|
||||||
import org.springframework.core.task.TaskExecutor;
|
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
|
* {@link EnableAutoConfiguration Auto-configuration} for Spring Batch using an in-memory
|
||||||
* found in the context, it will be executed on startup.
|
* store.
|
||||||
* <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 Stephane Nicoll
|
||||||
* @author Eddú Meléndez
|
|
||||||
* @author Kazuki Shimizu
|
|
||||||
* @author Mahmoud Ben Hassine
|
|
||||||
* @author Lars Uffmann
|
|
||||||
* @author Lasse Wulff
|
|
||||||
* @author Yanming Zhou
|
|
||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
*/
|
*/
|
||||||
@AutoConfiguration(after = { DataSourceAutoConfiguration.class, TransactionAutoConfiguration.class },
|
@AutoConfiguration
|
||||||
afterName = "org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration")
|
@ConditionalOnClass(JobOperator.class)
|
||||||
@ConditionalOnClass({ JobOperator.class, DataSource.class, DatabasePopulator.class })
|
|
||||||
@ConditionalOnBean({ DataSource.class, PlatformTransactionManager.class })
|
|
||||||
@ConditionalOnMissingBean(value = DefaultBatchConfiguration.class, annotation = EnableBatchProcessing.class)
|
@ConditionalOnMissingBean(value = DefaultBatchConfiguration.class, annotation = EnableBatchProcessing.class)
|
||||||
@EnableConfigurationProperties(BatchProperties.class)
|
@EnableConfigurationProperties(BatchProperties.class)
|
||||||
@Import(DatabaseInitializationDependencyConfigurer.class)
|
|
||||||
public final class BatchAutoConfiguration {
|
public final class BatchAutoConfiguration {
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
@ConditionalOnBooleanProperty(name = "spring.batch.job.enabled", matchIfMissing = true)
|
|
||||||
JobLauncherApplicationRunner jobLauncherApplicationRunner(JobOperator jobOperator, BatchProperties properties) {
|
|
||||||
JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(jobOperator);
|
|
||||||
String jobName = properties.getJob().getName();
|
|
||||||
if (StringUtils.hasText(jobName)) {
|
|
||||||
runner.setJobName(jobName);
|
|
||||||
}
|
|
||||||
return runner;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(ExitCodeGenerator.class)
|
|
||||||
JobExecutionExitCodeGenerator jobExecutionExitCodeGenerator() {
|
|
||||||
return new JobExecutionExitCodeGenerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
static class SpringBootBatchConfiguration extends JdbcDefaultBatchConfiguration {
|
static class SpringBootBatchDefaultConfiguration extends DefaultBatchConfiguration {
|
||||||
|
|
||||||
private final DataSource dataSource;
|
|
||||||
|
|
||||||
private final PlatformTransactionManager transactionManager;
|
|
||||||
|
|
||||||
private final @Nullable TaskExecutor taskExecutor;
|
private final @Nullable TaskExecutor taskExecutor;
|
||||||
|
|
||||||
private final BatchProperties properties;
|
|
||||||
|
|
||||||
private final List<BatchConversionServiceCustomizer> batchConversionServiceCustomizers;
|
|
||||||
|
|
||||||
private final @Nullable ExecutionContextSerializer executionContextSerializer;
|
|
||||||
|
|
||||||
private final @Nullable JobParametersConverter jobParametersConverter;
|
private final @Nullable JobParametersConverter jobParametersConverter;
|
||||||
|
|
||||||
SpringBootBatchConfiguration(DataSource dataSource, @BatchDataSource ObjectProvider<DataSource> batchDataSource,
|
SpringBootBatchDefaultConfiguration(@BatchTaskExecutor ObjectProvider<TaskExecutor> batchTaskExecutor,
|
||||||
PlatformTransactionManager transactionManager,
|
|
||||||
@BatchTransactionManager ObjectProvider<PlatformTransactionManager> batchTransactionManager,
|
|
||||||
@BatchTaskExecutor ObjectProvider<TaskExecutor> batchTaskExecutor, BatchProperties properties,
|
|
||||||
ObjectProvider<BatchConversionServiceCustomizer> batchConversionServiceCustomizers,
|
|
||||||
ObjectProvider<ExecutionContextSerializer> executionContextSerializer,
|
|
||||||
ObjectProvider<JobParametersConverter> jobParametersConverter) {
|
ObjectProvider<JobParametersConverter> jobParametersConverter) {
|
||||||
this.dataSource = batchDataSource.getIfAvailable(() -> dataSource);
|
|
||||||
this.transactionManager = batchTransactionManager.getIfAvailable(() -> transactionManager);
|
|
||||||
this.taskExecutor = batchTaskExecutor.getIfAvailable();
|
this.taskExecutor = batchTaskExecutor.getIfAvailable();
|
||||||
this.properties = properties;
|
|
||||||
this.batchConversionServiceCustomizers = batchConversionServiceCustomizers.orderedStream().toList();
|
|
||||||
this.executionContextSerializer = executionContextSerializer.getIfAvailable();
|
|
||||||
this.jobParametersConverter = jobParametersConverter.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
|
@Override
|
||||||
@Deprecated(since = "4.0.0", forRemoval = true)
|
@Deprecated(since = "4.0.0", forRemoval = true)
|
||||||
@SuppressWarnings("removal")
|
@SuppressWarnings("removal")
|
||||||
|
@ -188,26 +72,4 @@ public final class BatchAutoConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@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,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.batch.autoconfigure;
|
||||||
|
|
||||||
|
import org.springframework.batch.core.launch.JobOperator;
|
||||||
|
import org.springframework.boot.ExitCodeGenerator;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link EnableAutoConfiguration Auto-configuration} for Spring Batch. If a single job is
|
||||||
|
* found in the context, it will be executed on startup.
|
||||||
|
* <p>
|
||||||
|
* Disable this behavior with {@literal spring.batch.job.enabled=false}).
|
||||||
|
* <p>
|
||||||
|
* If multiple jobs are found, a job name to execute on startup can be supplied by the
|
||||||
|
* User with : {@literal spring.batch.job.name=job1}. In this case the Runner will first
|
||||||
|
* find jobs registered as Beans, then those in the existing JobRegistry.
|
||||||
|
*
|
||||||
|
* @author Dave Syer
|
||||||
|
* @author Eddú Meléndez
|
||||||
|
* @author Kazuki Shimizu
|
||||||
|
* @author Mahmoud Ben Hassine
|
||||||
|
* @author Lars Uffmann
|
||||||
|
* @author Lasse Wulff
|
||||||
|
* @author Yanming Zhou
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
@AutoConfiguration(after = BatchAutoConfiguration.class)
|
||||||
|
@ConditionalOnClass(JobOperator.class)
|
||||||
|
@ConditionalOnBean(JobOperator.class)
|
||||||
|
@EnableConfigurationProperties(BatchProperties.class)
|
||||||
|
public final class BatchJobLauncherAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
@ConditionalOnBooleanProperty(name = "spring.batch.job.enabled", matchIfMissing = true)
|
||||||
|
JobLauncherApplicationRunner jobLauncherApplicationRunner(JobOperator jobOperator, BatchProperties properties) {
|
||||||
|
JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(jobOperator);
|
||||||
|
String jobName = properties.getJob().getName();
|
||||||
|
if (StringUtils.hasText(jobName)) {
|
||||||
|
runner.setJobName(jobName);
|
||||||
|
}
|
||||||
|
return runner;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean(ExitCodeGenerator.class)
|
||||||
|
JobExecutionExitCodeGenerator jobExecutionExitCodeGenerator() {
|
||||||
|
return new JobExecutionExitCodeGenerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,11 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.boot.batch.autoconfigure;
|
package org.springframework.boot.batch.autoconfigure;
|
||||||
|
|
||||||
import org.jspecify.annotations.Nullable;
|
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
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.
|
* Configuration properties for Spring Batch.
|
||||||
|
@ -37,16 +33,10 @@ public class BatchProperties {
|
||||||
|
|
||||||
private final Job job = new Job();
|
private final Job job = new Job();
|
||||||
|
|
||||||
private final Jdbc jdbc = new Jdbc();
|
|
||||||
|
|
||||||
public Job getJob() {
|
public Job getJob() {
|
||||||
return this.job;
|
return this.job;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Jdbc getJdbc() {
|
|
||||||
return this.jdbc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Job {
|
public static class Job {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,90 +55,4 @@ public class BatchProperties {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 @Nullable Isolation isolationLevelForCreate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Path to the SQL file to use to initialize the database schema.
|
|
||||||
*/
|
|
||||||
private String schema = DEFAULT_SCHEMA_LOCATION;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Platform to use in initialization scripts if the @@platform@@ placeholder is
|
|
||||||
* used. Auto-detected by default.
|
|
||||||
*/
|
|
||||||
private @Nullable String platform;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Table prefix for all the batch meta-data tables.
|
|
||||||
*/
|
|
||||||
private @Nullable String tablePrefix;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Database schema initialization mode.
|
|
||||||
*/
|
|
||||||
private DatabaseInitializationMode initializeSchema = DatabaseInitializationMode.EMBEDDED;
|
|
||||||
|
|
||||||
public boolean isValidateTransactionState() {
|
|
||||||
return this.validateTransactionState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValidateTransactionState(boolean validateTransactionState) {
|
|
||||||
this.validateTransactionState = validateTransactionState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable Isolation getIsolationLevelForCreate() {
|
|
||||||
return this.isolationLevelForCreate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsolationLevelForCreate(@Nullable Isolation isolationLevelForCreate) {
|
|
||||||
this.isolationLevelForCreate = isolationLevelForCreate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSchema() {
|
|
||||||
return this.schema;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSchema(String schema) {
|
|
||||||
this.schema = schema;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable String getPlatform() {
|
|
||||||
return this.platform;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPlatform(@Nullable String platform) {
|
|
||||||
this.platform = platform;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable String getTablePrefix() {
|
|
||||||
return this.tablePrefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTablePrefix(@Nullable String tablePrefix) {
|
|
||||||
this.tablePrefix = tablePrefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatabaseInitializationMode getInitializeSchema() {
|
|
||||||
return this.initializeSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInitializeSchema(DatabaseInitializationMode initializeSchema) {
|
|
||||||
this.initializeSchema = initializeSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +1,10 @@
|
||||||
{
|
{
|
||||||
"properties": [
|
"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",
|
"name": "spring.batch.job.enabled",
|
||||||
"type": "java.lang.Boolean",
|
"type": "java.lang.Boolean",
|
||||||
"description": "Whether to execute a Spring Batch job on startup. When multiple jobs are present in the context, set spring.batch.job.name to identify the job to execute.",
|
"description": "Whether to execute a Spring Batch job on startup. When multiple jobs are present in the context, set spring.batch.job.name to identify the job to execute.",
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "spring.batch.schema",
|
|
||||||
"type": "java.lang.String",
|
|
||||||
"deprecation": {
|
|
||||||
"replacement": "spring.batch.jdbc.schema",
|
|
||||||
"level": "error"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "spring.batch.table-prefix",
|
|
||||||
"type": "java.lang.String",
|
|
||||||
"deprecation": {
|
|
||||||
"replacement": "spring.batch.jdbc.table-prefix",
|
|
||||||
"level": "error"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
org.springframework.boot.batch.autoconfigure.BatchAutoConfiguration
|
org.springframework.boot.batch.autoconfigure.BatchAutoConfiguration
|
||||||
|
org.springframework.boot.batch.autoconfigure.BatchJobLauncherAutoConfiguration
|
||||||
org.springframework.boot.batch.autoconfigure.observation.BatchObservationAutoConfiguration
|
org.springframework.boot.batch.autoconfigure.observation.BatchObservationAutoConfiguration
|
||||||
|
|
|
@ -17,86 +17,42 @@
|
||||||
package org.springframework.boot.batch.autoconfigure;
|
package org.springframework.boot.batch.autoconfigure;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
|
|
||||||
import jakarta.persistence.EntityManagerFactory;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
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.configuration.JobRegistry;
|
import org.springframework.batch.core.configuration.JobRegistry;
|
||||||
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
||||||
import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration;
|
import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration;
|
||||||
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
|
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
|
||||||
import org.springframework.batch.core.converter.JobParametersConverter;
|
import org.springframework.batch.core.converter.JobParametersConverter;
|
||||||
import org.springframework.batch.core.converter.JsonJobParametersConverter;
|
import org.springframework.batch.core.converter.JsonJobParametersConverter;
|
||||||
import org.springframework.batch.core.job.AbstractJob;
|
|
||||||
import org.springframework.batch.core.job.Job;
|
import org.springframework.batch.core.job.Job;
|
||||||
import org.springframework.batch.core.job.JobExecution;
|
|
||||||
import org.springframework.batch.core.job.parameters.JobParameters;
|
|
||||||
import org.springframework.batch.core.job.parameters.JobParametersBuilder;
|
|
||||||
import org.springframework.batch.core.launch.JobOperator;
|
import org.springframework.batch.core.launch.JobOperator;
|
||||||
import org.springframework.batch.core.repository.ExecutionContextSerializer;
|
|
||||||
import org.springframework.batch.core.repository.JobRepository;
|
import org.springframework.batch.core.repository.JobRepository;
|
||||||
import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer;
|
|
||||||
import org.springframework.batch.core.repository.dao.Jackson2ExecutionContextStringSerializer;
|
|
||||||
import org.springframework.batch.core.step.Step;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
|
||||||
import org.springframework.boot.CommandLineRunner;
|
|
||||||
import org.springframework.boot.DefaultApplicationArguments;
|
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
|
import org.springframework.boot.batch.autoconfigure.BatchAutoConfiguration.SpringBootBatchDefaultConfiguration;
|
||||||
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.hibernate.autoconfigure.HibernateJpaAutoConfiguration;
|
|
||||||
import org.springframework.boot.jdbc.DataSourceBuilder;
|
|
||||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
|
|
||||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
|
|
||||||
import org.springframework.boot.jdbc.autoconfigure.EmbeddedDataSourceConfiguration;
|
|
||||||
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
|
|
||||||
import org.springframework.boot.liquibase.autoconfigure.LiquibaseAutoConfiguration;
|
|
||||||
import org.springframework.boot.sql.init.DatabaseInitializationMode;
|
|
||||||
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
|
|
||||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
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.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Primary;
|
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
|
||||||
import org.springframework.core.task.AsyncTaskExecutor;
|
import org.springframework.core.task.AsyncTaskExecutor;
|
||||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||||
import org.springframework.core.task.SyncTaskExecutor;
|
import org.springframework.core.task.SyncTaskExecutor;
|
||||||
import org.springframework.core.task.TaskExecutor;
|
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.jdbc.datasource.init.DatabasePopulator;
|
||||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
|
||||||
import org.springframework.test.util.AopTestUtils;
|
import org.springframework.test.util.AopTestUtils;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
|
||||||
import org.springframework.transaction.annotation.Isolation;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link BatchAutoConfiguration}.
|
* Tests for {@link BatchJobLauncherAutoConfiguration}.
|
||||||
*
|
*
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
@ -110,336 +66,43 @@ import static org.mockito.Mockito.mock;
|
||||||
@ExtendWith(OutputCaptureExtension.class)
|
@ExtendWith(OutputCaptureExtension.class)
|
||||||
class BatchAutoConfigurationTests {
|
class BatchAutoConfigurationTests {
|
||||||
|
|
||||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
|
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||||
AutoConfigurations.of(BatchAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class,
|
.withConfiguration(AutoConfigurations.of(BatchAutoConfiguration.class));
|
||||||
TransactionAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class));
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDefaultContext() {
|
void testDefaultContext() {
|
||||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
|
this.contextRunner.run((context) -> {
|
||||||
assertThat(context).hasSingleBean(JobRepository.class);
|
assertThat(context).hasSingleBean(JobRepository.class);
|
||||||
assertThat(context).hasSingleBean(JobOperator.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
|
@Test
|
||||||
void autoConfigurationBacksOffWhenUserEnablesBatchProcessing() {
|
void autoConfigurationBacksOffWhenUserEnablesBatchProcessing() {
|
||||||
this.contextRunner
|
this.contextRunner.withUserConfiguration(EnableBatchProcessingConfiguration.class)
|
||||||
.withUserConfiguration(EnableBatchProcessingConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
.run((context) -> assertThat(context).doesNotHaveBean(SpringBootBatchDefaultConfiguration.class));
|
||||||
.withClassLoader(new FilteredClassLoader(DatabasePopulator.class))
|
|
||||||
.run((context) -> assertThat(context).doesNotHaveBean(SpringBootBatchConfiguration.class));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void autoConfigurationBacksOffWhenUserProvidesBatchConfiguration() {
|
void autoConfigurationBacksOffWhenUserProvidesBatchConfiguration() {
|
||||||
this.contextRunner.withUserConfiguration(CustomBatchConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
this.contextRunner.withUserConfiguration(CustomBatchConfiguration.class)
|
||||||
.withClassLoader(new FilteredClassLoader(DatabasePopulator.class))
|
.withClassLoader(new FilteredClassLoader(DatabasePopulator.class))
|
||||||
.run((context) -> assertThat(context).doesNotHaveBean(SpringBootBatchConfiguration.class));
|
.run((context) -> assertThat(context).doesNotHaveBean(SpringBootBatchDefaultConfiguration.class));
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDefinesAndLaunchesJob() {
|
|
||||||
this.contextRunner.withUserConfiguration(JobConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
|
||||||
.run((context) -> {
|
|
||||||
assertThat(context).hasSingleBean(JobOperator.class);
|
|
||||||
context.getBean(JobLauncherApplicationRunner.class)
|
|
||||||
.run(new DefaultApplicationArguments("jobParam=test"));
|
|
||||||
JobParameters jobParameters = new JobParametersBuilder().addString("jobParam", "test")
|
|
||||||
.toJobParameters();
|
|
||||||
assertThat(context.getBean(JobRepository.class).getLastJobExecution("job", jobParameters)).isNotNull();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDefinesAndLaunchesJobIgnoreOptionArguments() {
|
|
||||||
this.contextRunner.withUserConfiguration(JobConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
|
||||||
.run((context) -> {
|
|
||||||
assertThat(context).hasSingleBean(JobOperator.class);
|
|
||||||
context.getBean(JobLauncherApplicationRunner.class)
|
|
||||||
.run(new DefaultApplicationArguments("--spring.property=value", "jobParam=test"));
|
|
||||||
JobParameters jobParameters = new JobParametersBuilder().addString("jobParam", "test")
|
|
||||||
.toJobParameters();
|
|
||||||
assertThat(context.getBean(JobRepository.class).getLastJobExecution("job", jobParameters)).isNotNull();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testRegisteredAndLocalJob() {
|
|
||||||
this.contextRunner
|
|
||||||
.withUserConfiguration(NamedJobConfigurationWithRegisteredAndLocalJob.class,
|
|
||||||
EmbeddedDataSourceConfiguration.class)
|
|
||||||
.withPropertyValues("spring.batch.job.name:discreteRegisteredJob")
|
|
||||||
.run((context) -> {
|
|
||||||
assertThat(context).hasSingleBean(JobOperator.class);
|
|
||||||
context.getBean(JobLauncherApplicationRunner.class).run();
|
|
||||||
assertThat(context.getBean(JobRepository.class)
|
|
||||||
.getLastJobExecution("discreteRegisteredJob", new JobParameters())
|
|
||||||
.getStatus()).isEqualTo(BatchStatus.COMPLETED);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDefinesAndLaunchesLocalJob() {
|
|
||||||
this.contextRunner
|
|
||||||
.withUserConfiguration(NamedJobConfigurationWithLocalJob.class, EmbeddedDataSourceConfiguration.class)
|
|
||||||
.withPropertyValues("spring.batch.job.name:discreteLocalJob")
|
|
||||||
.run((context) -> {
|
|
||||||
assertThat(context).hasSingleBean(JobOperator.class);
|
|
||||||
context.getBean(JobLauncherApplicationRunner.class).run();
|
|
||||||
assertThat(context.getBean(JobRepository.class)
|
|
||||||
.getLastJobExecution("discreteLocalJob", new JobParameters())).isNotNull();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testMultipleJobsAndNoJobName() {
|
|
||||||
this.contextRunner.withUserConfiguration(MultipleJobConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
|
||||||
.run((context) -> {
|
|
||||||
assertThat(context).hasFailed();
|
|
||||||
assertThat(context.getStartupFailure().getCause().getMessage())
|
|
||||||
.contains("Job name must be specified in case of multiple jobs");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testMultipleJobsAndJobName() {
|
|
||||||
this.contextRunner.withUserConfiguration(MultipleJobConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
|
||||||
.withPropertyValues("spring.batch.job.name:discreteLocalJob")
|
|
||||||
.run((context) -> {
|
|
||||||
assertThat(context).hasSingleBean(JobOperator.class);
|
|
||||||
context.getBean(JobLauncherApplicationRunner.class).run();
|
|
||||||
assertThat(context.getBean(JobRepository.class)
|
|
||||||
.getLastJobExecution("discreteLocalJob", new JobParameters())).isNotNull();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDisableLaunchesJob() {
|
|
||||||
this.contextRunner.withUserConfiguration(JobConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
|
||||||
.withPropertyValues("spring.batch.job.enabled:false")
|
|
||||||
.run((context) -> {
|
|
||||||
assertThat(context).hasSingleBean(JobOperator.class);
|
|
||||||
assertThat(context).doesNotHaveBean(CommandLineRunner.class);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDisableSchemaLoader() {
|
|
||||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
|
|
||||||
.withPropertyValues("spring.datasource.generate-unique-name=true",
|
|
||||||
"spring.batch.jdbc.initialize-schema:never")
|
|
||||||
.run((context) -> {
|
|
||||||
assertThat(context).hasSingleBean(JobOperator.class);
|
|
||||||
assertThat(context.getBean(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(JobOperator.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();
|
|
||||||
JobRepository jobRepository = context.getBean(JobRepository.class);
|
|
||||||
assertThat(jobRepository.findRunningJobExecutions("test")).isEmpty();
|
|
||||||
assertThat(jobRepository.getLastJobExecution("test", new JobParameters())).isNull();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCustomizeJpaTransactionManagerUsingProperties() {
|
|
||||||
this.contextRunner
|
|
||||||
.withUserConfiguration(TestJpaConfiguration.class, EmbeddedDataSourceConfiguration.class,
|
|
||||||
HibernateJpaAutoConfiguration.class)
|
|
||||||
.withPropertyValues("spring.transaction.default-timeout:30",
|
|
||||||
"spring.transaction.rollback-on-commit-failure:true")
|
|
||||||
.run((context) -> {
|
|
||||||
assertThat(context).hasSingleBean(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
|
@Test
|
||||||
void testBatchTaskExecutor() {
|
void testBatchTaskExecutor() {
|
||||||
this.contextRunner
|
this.contextRunner.withUserConfiguration(BatchTaskExecutorConfiguration.class).run((context) -> {
|
||||||
.withUserConfiguration(BatchTaskExecutorConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
assertThat(context).hasSingleBean(SpringBootBatchDefaultConfiguration.class).hasBean("batchTaskExecutor");
|
||||||
.run((context) -> {
|
TaskExecutor batchTaskExecutor = context.getBean("batchTaskExecutor", TaskExecutor.class);
|
||||||
assertThat(context).hasSingleBean(SpringBootBatchConfiguration.class).hasBean("batchTaskExecutor");
|
assertThat(batchTaskExecutor).isInstanceOf(AsyncTaskExecutor.class);
|
||||||
TaskExecutor batchTaskExecutor = context.getBean("batchTaskExecutor", TaskExecutor.class);
|
assertThat(context.getBean(SpringBootBatchDefaultConfiguration.class).getTaskExecutor())
|
||||||
assertThat(batchTaskExecutor).isInstanceOf(AsyncTaskExecutor.class);
|
.isEqualTo(batchTaskExecutor);
|
||||||
assertThat(context.getBean(SpringBootBatchConfiguration.class).getTaskExecutor())
|
JobOperator jobOperator = AopTestUtils.getTargetObject(context.getBean(JobOperator.class));
|
||||||
.isEqualTo(batchTaskExecutor);
|
assertThat(jobOperator).hasFieldOrPropertyWithValue("taskExecutor", batchTaskExecutor);
|
||||||
JobOperator jobOperator = AopTestUtils.getTargetObject(context.getBean(JobOperator.class));
|
|
||||||
assertThat(jobOperator).hasFieldOrPropertyWithValue("taskExecutor", batchTaskExecutor);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void jobRepositoryBeansDependOnBatchDataSourceInitializer() {
|
|
||||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
|
|
||||||
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
|
|
||||||
String[] jobRepositoryNames = beanFactory.getBeanNamesForType(JobRepository.class);
|
|
||||||
assertThat(jobRepositoryNames).isNotEmpty();
|
|
||||||
for (String jobRepositoryName : jobRepositoryNames) {
|
|
||||||
assertThat(beanFactory.getBeanDefinition(jobRepositoryName).getDependsOn())
|
|
||||||
.contains("batchDataSourceInitializer");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void jobRepositoryBeansDependOnFlyway() {
|
|
||||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, FlywayAutoConfiguration.class)
|
|
||||||
.withPropertyValues("spring.batch.jdbc.initialize-schema=never")
|
|
||||||
.run((context) -> {
|
|
||||||
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
|
|
||||||
String[] jobRepositoryNames = beanFactory.getBeanNamesForType(JobRepository.class);
|
|
||||||
assertThat(jobRepositoryNames).isNotEmpty();
|
|
||||||
for (String jobRepositoryName : jobRepositoryNames) {
|
|
||||||
assertThat(beanFactory.getBeanDefinition(jobRepositoryName).getDependsOn()).contains("flyway",
|
|
||||||
"flywayInitializer");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@WithResource(name = "db/changelog/db.changelog-master.yaml", content = "databaseChangeLog:")
|
|
||||||
void jobRepositoryBeansDependOnLiquibase() {
|
|
||||||
this.contextRunner
|
|
||||||
.withUserConfiguration(EmbeddedDataSourceConfiguration.class, LiquibaseAutoConfiguration.class)
|
|
||||||
.withPropertyValues("spring.batch.jdbc.initialize-schema=never")
|
|
||||||
.run((context) -> {
|
|
||||||
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
|
|
||||||
String[] jobRepositoryNames = beanFactory.getBeanNamesForType(JobRepository.class);
|
|
||||||
assertThat(jobRepositoryNames).isNotEmpty();
|
|
||||||
for (String jobRepositoryName : jobRepositoryNames) {
|
|
||||||
assertThat(beanFactory.getBeanDefinition(jobRepositoryName).getDependsOn()).contains("liquibase");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void whenTheUserDefinesTheirOwnBatchDatabaseInitializerThenTheAutoConfiguredInitializerBacksOff() {
|
|
||||||
this.contextRunner.withUserConfiguration(CustomBatchDatabaseInitializerConfiguration.class)
|
|
||||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
|
|
||||||
DataSourceTransactionManagerAutoConfiguration.class))
|
|
||||||
.run((context) -> assertThat(context).hasSingleBean(BatchDataSourceScriptDatabaseInitializer.class)
|
|
||||||
.doesNotHaveBean("batchDataSourceScriptDatabaseInitializer")
|
|
||||||
.hasBean("customInitializer"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void whenTheUserDefinesTheirOwnDatabaseInitializerThenTheAutoConfiguredBatchInitializerRemains() {
|
|
||||||
this.contextRunner.withUserConfiguration(CustomDatabaseInitializerConfiguration.class)
|
|
||||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
|
|
||||||
DataSourceTransactionManagerAutoConfiguration.class))
|
|
||||||
.run((context) -> assertThat(context).hasSingleBean(BatchDataSourceScriptDatabaseInitializer.class)
|
|
||||||
.hasBean("customInitializer"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void conversionServiceCustomizersAreCalled() {
|
|
||||||
this.contextRunner
|
|
||||||
.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
|
|
||||||
ConversionServiceCustomizersConfiguration.class)
|
|
||||||
.run((context) -> {
|
|
||||||
BatchConversionServiceCustomizer customizer = context.getBean("batchConversionServiceCustomizer",
|
|
||||||
BatchConversionServiceCustomizer.class);
|
|
||||||
BatchConversionServiceCustomizer anotherCustomizer = context
|
|
||||||
.getBean("anotherBatchConversionServiceCustomizer", BatchConversionServiceCustomizer.class);
|
|
||||||
InOrder inOrder = Mockito.inOrder(customizer, anotherCustomizer);
|
|
||||||
ConfigurableConversionService configurableConversionService = context
|
|
||||||
.getBean(SpringBootBatchConfiguration.class)
|
|
||||||
.getConversionService();
|
|
||||||
inOrder.verify(customizer).customize(configurableConversionService);
|
|
||||||
inOrder.verify(anotherCustomizer).customize(configurableConversionService);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void whenTheUserDefinesAJobNameAsJobInstanceValidates() {
|
void whenTheUserDefinesAJobNameAsJobInstanceValidates() {
|
||||||
JobLauncherApplicationRunner runner = createInstance("another");
|
JobLauncherApplicationRunner runner = createInstance("another");
|
||||||
|
@ -472,60 +135,24 @@ class BatchAutoConfigurationTests {
|
||||||
.withMessage("No job found with name 'three'");
|
.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
|
@Test
|
||||||
@Deprecated(since = "4.0.0", forRemoval = true)
|
@Deprecated(since = "4.0.0", forRemoval = true)
|
||||||
@SuppressWarnings("removal")
|
@SuppressWarnings("removal")
|
||||||
void customJobParametersConverterIsUsed() {
|
void customJobParametersConverterIsUsed() {
|
||||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
|
this.contextRunner.withBean(JobParametersConverter.class, JsonJobParametersConverter::new).run((context) -> {
|
||||||
.withBean(JobParametersConverter.class, JsonJobParametersConverter::new)
|
assertThat(context).hasSingleBean(JsonJobParametersConverter.class);
|
||||||
.withPropertyValues("spring.datasource.generate-unique-name=true")
|
assertThat(context.getBean(SpringBootBatchDefaultConfiguration.class).getJobParametersConverter())
|
||||||
.run((context) -> {
|
.isInstanceOf(JsonJobParametersConverter.class);
|
||||||
assertThat(context).hasSingleBean(JsonJobParametersConverter.class);
|
});
|
||||||
assertThat(context.getBean(SpringBootBatchConfiguration.class).getJobParametersConverter())
|
|
||||||
.isInstanceOf(JsonJobParametersConverter.class);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Deprecated(since = "4.0.0", forRemoval = true)
|
@Deprecated(since = "4.0.0", forRemoval = true)
|
||||||
@SuppressWarnings("removal")
|
@SuppressWarnings("removal")
|
||||||
void defaultJobParametersConverterIsUsed() {
|
void defaultJobParametersConverterIsUsed() {
|
||||||
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
|
this.contextRunner.run((context) -> {
|
||||||
assertThat(context).doesNotHaveBean(JobParametersConverter.class);
|
assertThat(context).doesNotHaveBean(JobParametersConverter.class);
|
||||||
assertThat(context.getBean(SpringBootBatchConfiguration.class).getJobParametersConverter())
|
assertThat(context.getBean(SpringBootBatchDefaultConfiguration.class).getJobParametersConverter())
|
||||||
.isInstanceOf(DefaultJobParametersConverter.class);
|
.isInstanceOf(DefaultJobParametersConverter.class);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -544,44 +171,6 @@ class BatchAutoConfigurationTests {
|
||||||
return job;
|
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)
|
@Configuration(proxyBeanMethods = false)
|
||||||
static class BatchTaskExecutorConfiguration {
|
static class BatchTaskExecutorConfiguration {
|
||||||
|
|
||||||
|
@ -603,188 +192,6 @@ class BatchAutoConfigurationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestAutoConfigurationPackage(City.class)
|
|
||||||
static class TestJpaConfiguration {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
static class EntityManagerFactoryConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
EntityManagerFactory entityManagerFactory() {
|
|
||||||
return mock(EntityManagerFactory.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
static class NamedJobConfigurationWithRegisteredAndLocalJob {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private JobRepository jobRepository;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
Job discreteJob() {
|
|
||||||
AbstractJob job = new AbstractJob("discreteRegisteredJob") {
|
|
||||||
|
|
||||||
private static int count = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<String> getStepNames() {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Step getStep(String stepName) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doExecute(JobExecution execution) {
|
|
||||||
if (count == 0) {
|
|
||||||
execution.setStatus(BatchStatus.COMPLETED);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
execution.setStatus(BatchStatus.FAILED);
|
|
||||||
}
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
job.setJobRepository(this.jobRepository);
|
|
||||||
return job;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
static class NamedJobConfigurationWithLocalJob {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private JobRepository jobRepository;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
Job discreteJob() {
|
|
||||||
AbstractJob job = new AbstractJob("discreteLocalJob") {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<String> getStepNames() {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Step getStep(String stepName) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doExecute(JobExecution execution) {
|
|
||||||
execution.setStatus(BatchStatus.COMPLETED);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
job.setJobRepository(this.jobRepository);
|
|
||||||
return job;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
static class MultipleJobConfiguration {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private JobRepository jobRepository;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
Job discreteJob() {
|
|
||||||
AbstractJob job = new AbstractJob("discreteLocalJob") {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<String> getStepNames() {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Step getStep(String stepName) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doExecute(JobExecution execution) {
|
|
||||||
execution.setStatus(BatchStatus.COMPLETED);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
job.setJobRepository(this.jobRepository);
|
|
||||||
return job;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
Job job2() {
|
|
||||||
return new Job() {
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "discreteLocalJob2";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(JobExecution execution) {
|
|
||||||
execution.setStatus(BatchStatus.COMPLETED);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
static class JobConfiguration {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private JobRepository jobRepository;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
Job job() {
|
|
||||||
AbstractJob job = new AbstractJob() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<String> getStepNames() {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Step getStep(String stepName) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doExecute(JobExecution execution) {
|
|
||||||
execution.setStatus(BatchStatus.COMPLETED);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
job.setJobRepository(this.jobRepository);
|
|
||||||
return job;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
static class CustomBatchDatabaseInitializerConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
BatchDataSourceScriptDatabaseInitializer customInitializer(DataSource dataSource, 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)
|
@Configuration(proxyBeanMethods = false)
|
||||||
static class CustomBatchConfiguration extends DefaultBatchConfiguration {
|
static class CustomBatchConfiguration extends DefaultBatchConfiguration {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,277 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.batch.autoconfigure;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.batch.core.BatchStatus;
|
||||||
|
import org.springframework.batch.core.job.AbstractJob;
|
||||||
|
import org.springframework.batch.core.job.Job;
|
||||||
|
import org.springframework.batch.core.job.JobExecution;
|
||||||
|
import org.springframework.batch.core.job.parameters.JobParameters;
|
||||||
|
import org.springframework.batch.core.job.parameters.JobParametersBuilder;
|
||||||
|
import org.springframework.batch.core.launch.JobOperator;
|
||||||
|
import org.springframework.batch.core.repository.JobRepository;
|
||||||
|
import org.springframework.batch.core.step.Step;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.boot.DefaultApplicationArguments;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link BatchJobLauncherAutoConfiguration}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class BatchJobLauncherAutoConfigurationTests {
|
||||||
|
|
||||||
|
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
|
||||||
|
AutoConfigurations.of(BatchAutoConfiguration.class, BatchJobLauncherAutoConfiguration.class));
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDefinesAndLaunchesJob() {
|
||||||
|
this.contextRunner.withUserConfiguration(JobConfiguration.class).run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
context.getBean(JobLauncherApplicationRunner.class).run(new DefaultApplicationArguments("jobParam=test"));
|
||||||
|
JobParameters jobParameters = new JobParametersBuilder().addString("jobParam", "test").toJobParameters();
|
||||||
|
assertThat(context.getBean(JobRepository.class).getLastJobExecution("job", jobParameters)).isNotNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDefinesAndLaunchesJobIgnoreOptionArguments() {
|
||||||
|
this.contextRunner.withUserConfiguration(JobConfiguration.class).run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
context.getBean(JobLauncherApplicationRunner.class)
|
||||||
|
.run(new DefaultApplicationArguments("--spring.property=value", "jobParam=test"));
|
||||||
|
JobParameters jobParameters = new JobParametersBuilder().addString("jobParam", "test").toJobParameters();
|
||||||
|
assertThat(context.getBean(JobRepository.class).getLastJobExecution("job", jobParameters)).isNotNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRegisteredAndLocalJob() {
|
||||||
|
this.contextRunner.withUserConfiguration(NamedJobConfigurationWithRegisteredAndLocalJob.class)
|
||||||
|
.withPropertyValues("spring.batch.job.name:discreteRegisteredJob")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
context.getBean(JobLauncherApplicationRunner.class).run();
|
||||||
|
assertThat(context.getBean(JobRepository.class)
|
||||||
|
.getLastJobExecution("discreteRegisteredJob", new JobParameters())
|
||||||
|
.getStatus()).isEqualTo(BatchStatus.COMPLETED);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDefinesAndLaunchesLocalJob() {
|
||||||
|
this.contextRunner.withUserConfiguration(NamedJobConfigurationWithLocalJob.class)
|
||||||
|
.withPropertyValues("spring.batch.job.name:discreteLocalJob")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
context.getBean(JobLauncherApplicationRunner.class).run();
|
||||||
|
assertThat(context.getBean(JobRepository.class)
|
||||||
|
.getLastJobExecution("discreteLocalJob", new JobParameters())).isNotNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMultipleJobsAndNoJobName() {
|
||||||
|
this.contextRunner.withUserConfiguration(MultipleJobConfiguration.class).run((context) -> {
|
||||||
|
assertThat(context).hasFailed();
|
||||||
|
assertThat(context.getStartupFailure().getCause().getMessage())
|
||||||
|
.contains("Job name must be specified in case of multiple jobs");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMultipleJobsAndJobName() {
|
||||||
|
this.contextRunner.withUserConfiguration(MultipleJobConfiguration.class)
|
||||||
|
.withPropertyValues("spring.batch.job.name:discreteLocalJob")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
context.getBean(JobLauncherApplicationRunner.class).run();
|
||||||
|
assertThat(context.getBean(JobRepository.class)
|
||||||
|
.getLastJobExecution("discreteLocalJob", new JobParameters())).isNotNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDisableLaunchesJob() {
|
||||||
|
this.contextRunner.withUserConfiguration(JobConfiguration.class)
|
||||||
|
.withPropertyValues("spring.batch.job.enabled:false")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(JobOperator.class);
|
||||||
|
assertThat(context).doesNotHaveBean(CommandLineRunner.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class NamedJobConfigurationWithRegisteredAndLocalJob {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobRepository jobRepository;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Job discreteJob() {
|
||||||
|
AbstractJob job = new AbstractJob("discreteRegisteredJob") {
|
||||||
|
|
||||||
|
private static int count = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getStepNames() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Step getStep(String stepName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doExecute(JobExecution execution) {
|
||||||
|
if (count == 0) {
|
||||||
|
execution.setStatus(BatchStatus.COMPLETED);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
execution.setStatus(BatchStatus.FAILED);
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
job.setJobRepository(this.jobRepository);
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class NamedJobConfigurationWithLocalJob {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobRepository jobRepository;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Job discreteJob() {
|
||||||
|
AbstractJob job = new AbstractJob("discreteLocalJob") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getStepNames() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Step getStep(String stepName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doExecute(JobExecution execution) {
|
||||||
|
execution.setStatus(BatchStatus.COMPLETED);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
job.setJobRepository(this.jobRepository);
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class MultipleJobConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobRepository jobRepository;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Job discreteJob() {
|
||||||
|
AbstractJob job = new AbstractJob("discreteLocalJob") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getStepNames() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Step getStep(String stepName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doExecute(JobExecution execution) {
|
||||||
|
execution.setStatus(BatchStatus.COMPLETED);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
job.setJobRepository(this.jobRepository);
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Job job2() {
|
||||||
|
return new Job() {
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "discreteLocalJob2";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(JobExecution execution) {
|
||||||
|
execution.setStatus(BatchStatus.COMPLETED);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class JobConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobRepository jobRepository;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Job job() {
|
||||||
|
AbstractJob job = new AbstractJob() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getStepNames() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Step getStep(String stepName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doExecute(JobExecution execution) {
|
||||||
|
execution.setStatus(BatchStatus.COMPLETED);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
job.setJobRepository(this.jobRepository);
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,44 +16,30 @@
|
||||||
|
|
||||||
package org.springframework.boot.batch.autoconfigure;
|
package org.springframework.boot.batch.autoconfigure;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.batch.core.ExitStatus;
|
||||||
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
||||||
import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository;
|
|
||||||
import org.springframework.batch.core.job.Job;
|
import org.springframework.batch.core.job.Job;
|
||||||
|
import org.springframework.batch.core.job.JobExecution;
|
||||||
import org.springframework.batch.core.job.JobExecutionException;
|
import org.springframework.batch.core.job.JobExecutionException;
|
||||||
import org.springframework.batch.core.job.JobInstance;
|
import org.springframework.batch.core.job.JobInstance;
|
||||||
import org.springframework.batch.core.job.builder.JobBuilder;
|
import org.springframework.batch.core.job.builder.JobBuilder;
|
||||||
import org.springframework.batch.core.job.builder.SimpleJobBuilder;
|
|
||||||
import org.springframework.batch.core.job.parameters.JobParameters;
|
import org.springframework.batch.core.job.parameters.JobParameters;
|
||||||
import org.springframework.batch.core.job.parameters.JobParametersBuilder;
|
|
||||||
import org.springframework.batch.core.launch.JobOperator;
|
import org.springframework.batch.core.launch.JobOperator;
|
||||||
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
|
||||||
import org.springframework.batch.core.repository.JobRepository;
|
import org.springframework.batch.core.repository.JobRepository;
|
||||||
import org.springframework.batch.core.repository.JobRestartException;
|
|
||||||
import org.springframework.batch.core.step.Step;
|
import org.springframework.batch.core.step.Step;
|
||||||
import org.springframework.batch.core.step.builder.StepBuilder;
|
import org.springframework.batch.core.step.builder.StepBuilder;
|
||||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||||
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.test.context.runner.ApplicationContextRunner;
|
||||||
import org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
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}.
|
* Tests for {@link JobLauncherApplicationRunner}.
|
||||||
|
@ -66,120 +52,37 @@ import static org.assertj.core.api.Assertions.fail;
|
||||||
class JobLauncherApplicationRunnerTests {
|
class JobLauncherApplicationRunnerTests {
|
||||||
|
|
||||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, TransactionAutoConfiguration.class,
|
.withBean(PlatformTransactionManager.class, ResourcelessTransactionManager::new)
|
||||||
DataSourceTransactionManagerAutoConfiguration.class))
|
|
||||||
.withUserConfiguration(BatchConfiguration.class);
|
.withUserConfiguration(BatchConfiguration.class);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void basicExecution() {
|
void basicExecutionSuccess() {
|
||||||
this.contextRunner.run((context) -> {
|
this.contextRunner.run((context) -> {
|
||||||
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
|
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
|
||||||
jobLauncherContext.executeJob(new JobParameters());
|
jobLauncherContext.executeJob(new JobParameters());
|
||||||
assertThat(jobLauncherContext.jobInstances()).hasSize(1);
|
List<JobInstance> jobInstances = jobLauncherContext.jobInstances();
|
||||||
jobLauncherContext.executeJob(new JobParametersBuilder().addLong("id", 1L).toJobParameters());
|
assertThat(jobInstances).hasSize(1);
|
||||||
assertThat(jobLauncherContext.jobInstances()).hasSize(2);
|
List<JobExecution> jobExecutions = jobLauncherContext.jobExecutions(jobInstances.get(0));
|
||||||
|
assertThat(jobExecutions).hasSize(1);
|
||||||
|
assertThat(jobExecutions.get(0).getExitStatus().getExitCode())
|
||||||
|
.isEqualTo(ExitStatus.COMPLETED.getExitCode());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void incrementExistingExecution() {
|
void basicExecutionFailure() {
|
||||||
this.contextRunner.run((context) -> {
|
this.contextRunner.run((context) -> {
|
||||||
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
|
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
|
||||||
Job job = jobLauncherContext.configureJob().incrementer(new RunIdIncrementer()).build();
|
|
||||||
JobParameters jobParameters = new JobParameters();
|
|
||||||
jobLauncherContext.runner.execute(job, jobParameters);
|
|
||||||
jobLauncherContext.runner.execute(job, jobParameters);
|
|
||||||
assertThat(jobLauncherContext.jobInstances()).hasSize(2);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void retryFailedExecutionWithIncrementer() {
|
|
||||||
this.contextRunner.run((context) -> {
|
|
||||||
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
|
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
|
||||||
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
|
|
||||||
Job job = jobLauncherContext.jobBuilder()
|
Job job = jobLauncherContext.jobBuilder()
|
||||||
.start(jobLauncherContext.stepBuilder().tasklet(throwingTasklet(), transactionManager).build())
|
.start(jobLauncherContext.stepBuilder().tasklet(throwingTasklet(), transactionManager).build())
|
||||||
.incrementer(new RunIdIncrementer())
|
|
||||||
.build();
|
.build();
|
||||||
jobLauncherContext.runner.execute(job, new JobParameters());
|
jobLauncherContext.runner.execute(job, new JobParameters());
|
||||||
jobLauncherContext.runner.execute(job, new JobParameters());
|
List<JobInstance> jobInstances = jobLauncherContext.jobInstances();
|
||||||
// with an incrementer, we always create a new job instance
|
assertThat(jobInstances).hasSize(1);
|
||||||
assertThat(jobLauncherContext.jobInstances()).hasSize(2);
|
List<JobExecution> jobExecutions = jobLauncherContext.jobExecutions(jobInstances.get(0));
|
||||||
});
|
assertThat(jobExecutions).hasSize(1);
|
||||||
}
|
assertThat(jobExecutions.get(0).getExitStatus().getExitCode()).isEqualTo(ExitStatus.FAILED.getExitCode());
|
||||||
|
|
||||||
@Test
|
|
||||||
void retryFailedExecutionWithoutIncrementer() {
|
|
||||||
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();
|
|
||||||
JobParameters jobParameters = new JobParametersBuilder().addLong("run.id", 1L).toJobParameters();
|
|
||||||
jobLauncherContext.runner.execute(job, jobParameters);
|
|
||||||
jobLauncherContext.runner.execute(job, jobParameters);
|
|
||||||
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())
|
|
||||||
.build();
|
|
||||||
JobParameters jobParameters = new JobParametersBuilder().addString("name", "foo").toJobParameters();
|
|
||||||
jobLauncherContext.runner.execute(job, jobParameters);
|
|
||||||
assertThat(jobLauncherContext.jobInstances()).hasSize(1);
|
|
||||||
assertThatExceptionOfType(JobRestartException.class).isThrownBy(() -> {
|
|
||||||
// try to re-run a failed execution
|
|
||||||
jobLauncherContext.runner.execute(job, jobParameters);
|
|
||||||
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())
|
|
||||||
.build();
|
|
||||||
JobParameters jobParameters = new JobParametersBuilder().addLong("run.id", 1L, true)
|
|
||||||
.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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,16 +104,14 @@ class JobLauncherApplicationRunnerTests {
|
||||||
|
|
||||||
private final StepBuilder stepBuilder;
|
private final StepBuilder stepBuilder;
|
||||||
|
|
||||||
private final Step step;
|
|
||||||
|
|
||||||
JobLauncherApplicationRunnerContext(ApplicationContext context) {
|
JobLauncherApplicationRunnerContext(ApplicationContext context) {
|
||||||
JobOperator jobOperator = context.getBean(JobOperator.class);
|
JobOperator jobOperator = context.getBean(JobOperator.class);
|
||||||
JobRepository jobRepository = context.getBean(JobRepository.class);
|
JobRepository jobRepository = context.getBean(JobRepository.class);
|
||||||
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
|
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
|
||||||
this.stepBuilder = new StepBuilder("step", jobRepository);
|
this.stepBuilder = new StepBuilder("step", jobRepository);
|
||||||
this.step = this.stepBuilder.tasklet((contribution, chunkContext) -> null, transactionManager).build();
|
Step step = this.stepBuilder.tasklet((contribution, chunkContext) -> null, transactionManager).build();
|
||||||
this.jobBuilder = new JobBuilder("job", jobRepository);
|
this.jobBuilder = new JobBuilder("job", jobRepository);
|
||||||
this.job = this.jobBuilder.start(this.step).build();
|
this.job = this.jobBuilder.start(step).build();
|
||||||
this.jobRepository = context.getBean(JobRepository.class);
|
this.jobRepository = context.getBean(JobRepository.class);
|
||||||
this.runner = new JobLauncherApplicationRunner(jobOperator);
|
this.runner = new JobLauncherApplicationRunner(jobOperator);
|
||||||
}
|
}
|
||||||
|
@ -219,6 +120,10 @@ class JobLauncherApplicationRunnerTests {
|
||||||
return this.jobRepository.getJobInstances("job", 0, 100);
|
return this.jobRepository.getJobInstances("job", 0, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<JobExecution> jobExecutions(JobInstance jobInstance) {
|
||||||
|
return this.jobRepository.getJobExecutions(jobInstance);
|
||||||
|
}
|
||||||
|
|
||||||
void executeJob(JobParameters jobParameters) throws JobExecutionException {
|
void executeJob(JobParameters jobParameters) throws JobExecutionException {
|
||||||
this.runner.execute(this.job, jobParameters);
|
this.runner.execute(this.job, jobParameters);
|
||||||
}
|
}
|
||||||
|
@ -231,30 +136,12 @@ class JobLauncherApplicationRunnerTests {
|
||||||
return this.stepBuilder;
|
return this.stepBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
SimpleJobBuilder configureJob() {
|
|
||||||
return this.jobBuilder.start(this.step);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnableBatchProcessing
|
|
||||||
@EnableJdbcJobRepository
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@EnableBatchProcessing
|
||||||
static class BatchConfiguration {
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,7 @@ include "module:spring-boot-artemis"
|
||||||
include "module:spring-boot-autoconfigure-classic"
|
include "module:spring-boot-autoconfigure-classic"
|
||||||
include "module:spring-boot-autoconfigure-classic-modules"
|
include "module:spring-boot-autoconfigure-classic-modules"
|
||||||
include "module:spring-boot-batch"
|
include "module:spring-boot-batch"
|
||||||
|
include "module:spring-boot-batch-jdbc"
|
||||||
include "module:spring-boot-cache"
|
include "module:spring-boot-cache"
|
||||||
include "module:spring-boot-cache-test"
|
include "module:spring-boot-cache-test"
|
||||||
include "module:spring-boot-cassandra"
|
include "module:spring-boot-cassandra"
|
||||||
|
@ -204,6 +205,7 @@ include "starter:spring-boot-starter-amqp"
|
||||||
include "starter:spring-boot-starter-artemis"
|
include "starter:spring-boot-starter-artemis"
|
||||||
include "starter:spring-boot-starter-aspectj"
|
include "starter:spring-boot-starter-aspectj"
|
||||||
include "starter:spring-boot-starter-batch"
|
include "starter:spring-boot-starter-batch"
|
||||||
|
include "starter:spring-boot-starter-batch-jdbc"
|
||||||
include "starter:spring-boot-starter-cache"
|
include "starter:spring-boot-starter-cache"
|
||||||
include "starter:spring-boot-starter-cassandra"
|
include "starter:spring-boot-starter-cassandra"
|
||||||
include "starter:spring-boot-starter-classic"
|
include "starter:spring-boot-starter-classic"
|
||||||
|
@ -317,6 +319,7 @@ include ":smoke-test:spring-boot-smoke-test-artemis"
|
||||||
include ":smoke-test:spring-boot-smoke-test-aspectj"
|
include ":smoke-test:spring-boot-smoke-test-aspectj"
|
||||||
include ":smoke-test:spring-boot-smoke-test-autoconfigure-classic"
|
include ":smoke-test:spring-boot-smoke-test-autoconfigure-classic"
|
||||||
include ":smoke-test:spring-boot-smoke-test-batch"
|
include ":smoke-test:spring-boot-smoke-test-batch"
|
||||||
|
include ":smoke-test:spring-boot-smoke-test-batch-jdbc"
|
||||||
include ":smoke-test:spring-boot-smoke-test-bootstrap-registry"
|
include ":smoke-test:spring-boot-smoke-test-bootstrap-registry"
|
||||||
include ":smoke-test:spring-boot-smoke-test-cache"
|
include ":smoke-test:spring-boot-smoke-test-cache"
|
||||||
include ":smoke-test:spring-boot-smoke-test-config"
|
include ":smoke-test:spring-boot-smoke-test-config"
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the License);
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "java"
|
||||||
|
}
|
||||||
|
|
||||||
|
description = "Spring Boot Batch with JDBC smoke test"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":starter:spring-boot-starter-batch-jdbc"))
|
||||||
|
|
||||||
|
runtimeOnly("org.hsqldb:hsqldb")
|
||||||
|
|
||||||
|
testImplementation(project(":starter:spring-boot-starter-test"))
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package smoketest.batch;
|
||||||
|
|
||||||
|
import org.springframework.batch.core.job.Job;
|
||||||
|
import org.springframework.batch.core.job.builder.JobBuilder;
|
||||||
|
import org.springframework.batch.core.repository.JobRepository;
|
||||||
|
import org.springframework.batch.core.step.Step;
|
||||||
|
import org.springframework.batch.core.step.builder.StepBuilder;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class SampleBatchApplication {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Tasklet tasklet() {
|
||||||
|
return (contribution, context) -> RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Job job(JobRepository jobRepository, Step step) {
|
||||||
|
return new JobBuilder("job", jobRepository).start(step).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Step step1(JobRepository jobRepository, Tasklet tasklet, PlatformTransactionManager transactionManager) {
|
||||||
|
return new StepBuilder("step1", jobRepository).tasklet(tasklet, transactionManager).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// System.exit is common for Batch applications since the exit code can be used to
|
||||||
|
// drive a workflow
|
||||||
|
System.exit(SpringApplication.exit(SpringApplication.run(SampleBatchApplication.class, args)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@NullMarked
|
||||||
|
package smoketest.batch;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package smoketest.batch;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.test.system.CapturedOutput;
|
||||||
|
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@ExtendWith(OutputCaptureExtension.class)
|
||||||
|
class SampleBatchApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDefaultSettings(CapturedOutput output) {
|
||||||
|
assertThat(SpringApplication.exit(SpringApplication.run(SampleBatchApplication.class))).isZero();
|
||||||
|
assertThat(output).contains("completed with the following parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,7 +23,5 @@ description = "Spring Boot Batch smoke test"
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":starter:spring-boot-starter-batch"))
|
implementation(project(":starter:spring-boot-starter-batch"))
|
||||||
|
|
||||||
runtimeOnly("org.hsqldb:hsqldb")
|
|
||||||
|
|
||||||
testImplementation(project(":starter:spring-boot-starter-test"))
|
testImplementation(project(":starter:spring-boot-starter-test"))
|
||||||
}
|
}
|
|
@ -26,7 +26,6 @@ import org.springframework.batch.repeat.RepeatStatus;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class SampleBatchApplication {
|
public class SampleBatchApplication {
|
||||||
|
@ -42,8 +41,8 @@ public class SampleBatchApplication {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
Step step1(JobRepository jobRepository, Tasklet tasklet, PlatformTransactionManager transactionManager) {
|
Step step1(JobRepository jobRepository, Tasklet tasklet) {
|
||||||
return new StepBuilder("step1", jobRepository).tasklet(tasklet, transactionManager).build();
|
return new StepBuilder("step1", jobRepository).tasklet(tasklet).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the License);
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "org.springframework.boot.starter"
|
||||||
|
}
|
||||||
|
|
||||||
|
description = "Starter for using Spring Batch with JDBC"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(":starter:spring-boot-starter"))
|
||||||
|
api(project(":starter:spring-boot-starter-jdbc"))
|
||||||
|
|
||||||
|
api(project(":module:spring-boot-batch-jdbc"))
|
||||||
|
}
|
|
@ -22,8 +22,6 @@ description = "Starter for using Spring Batch"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":starter:spring-boot-starter"))
|
api(project(":starter:spring-boot-starter"))
|
||||||
api(project(":starter:spring-boot-starter-jdbc"))
|
|
||||||
|
|
||||||
api(project(":module:spring-boot-batch"))
|
api(project(":module:spring-boot-batch"))
|
||||||
api(project(":module:spring-boot-jdbc"))
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue