From beceb883b29c402a3c6c8db68a81bdabab86b269 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 14 Sep 2022 10:39:21 +0200 Subject: [PATCH] Revisit the configuration code of EnableBatchProcessing Before this commit, the configuration of infrastructure beans was confusing and not straightforward to customize. This commit changes the way Batch infrastructure beans are configured. The most important changes are: * EnableBatchProcessing now provides new attributes to configure properties of infrastructure beans * Bean registration is now done programmatically with a BeanDefinitionRegistrar instead of importing a class with statically annotated bean definition methods * Bean are now resolved from the application context directly instead of being resolved from a BatchConfigurer * Both a data source and a transaction manager are now required to be defined in the application context * AbstractBatchConfiguration is now intended to be extended by users code to get/customize basic infrastructure beans Issue #3942 Revisit the configuration code of EnableBatchProcessing Before this commit, the configuration of infrastructure beans was confusing and not straightforward to customize. This commit changes the way Batch infrastructure beans are configured. The most important changes are: * EnableBatchProcessing now provides new attributes to configure properties of infrastructure beans * Bean registration is now done programmatically with a BeanDefinitionRegistrar instead of importing a class with statically annotated bean definition methods * Bean are now resolved from the application context directly instead of being resolved from a BatchConfigurer * Both a data source and a transaction manager are now required to be defined in the application context * AbstractBatchConfiguration is now intended to be extended by users code to get/customize basic infrastructure beans Issue #3942 --- .../BatchConfigurationException.java | 17 + .../AbstractBatchConfiguration.java | 145 -------- ...utomaticJobRegistrarBeanPostProcessor.java | 59 +++ .../BatchConfigurationSelector.java | 53 --- .../annotation/BatchConfigurer.java | 51 --- .../annotation/BatchRegistrar.java | 227 ++++++++++++ .../annotation/DefaultBatchConfigurer.java | 175 --------- .../annotation/EnableBatchProcessing.java | 131 ++++--- .../annotation/ModularBatchConfiguration.java | 61 --- .../annotation/SimpleBatchConfiguration.java | 56 --- .../support/AbstractBatchConfiguration.java | 346 ++++++++++++++++++ .../ScopeConfiguration.java | 3 +- .../configuration/xml/CoreNamespaceUtils.java | 2 +- .../support/JobRepositoryFactoryBean.java | 12 +- .../annotation/BatchRegistrarTests.java | 187 ++++++++++ .../annotation/DataSourceConfiguration.java | 30 +- .../JobBuilderConfigurationTests.java | 46 --- .../JobScopeConfigurationTests.java | 1 + ...ConfigurationWithBatchConfigurerTests.java | 109 ------ ...figurationWithoutBatchConfigurerTests.java | 160 -------- .../AbstractBatchConfigurationTests.java | 148 ++++++++ .../SimpleJobExplorerIntegrationTests.java | 8 + .../ConcurrentTransactionTests.java | 55 +-- .../JobLauncherParserTestsConfiguration.java | 8 +- ...tePartitioningManagerStepBuilderTests.java | 6 + 25 files changed, 1127 insertions(+), 969 deletions(-) delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AbstractBatchConfiguration.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AutomaticJobRegistrarBeanPostProcessor.java delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchConfigurationSelector.java delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchConfigurer.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/DefaultBatchConfigurer.java delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/ModularBatchConfiguration.java delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/SimpleBatchConfiguration.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractBatchConfiguration.java rename spring-batch-core/src/main/java/org/springframework/batch/core/configuration/{annotation => support}/ScopeConfiguration.java (94%) create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java delete mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationWithBatchConfigurerTests.java delete mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationWithoutBatchConfigurerTests.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AbstractBatchConfigurationTests.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/BatchConfigurationException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/BatchConfigurationException.java index 1d568810cd..1a8ad98035 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/BatchConfigurationException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/BatchConfigurationException.java @@ -36,4 +36,21 @@ public BatchConfigurationException(Throwable t) { super(t); } + /** + * Create an exception with the given message. + * @param message the error message + */ + public BatchConfigurationException(String message) { + super(message); + } + + /** + * Create an exception with the given message and {@link Throwable}. + * @param message the error message + * @param cause an exception to be wrapped + */ + public BatchConfigurationException(String message, Throwable cause) { + super(message, cause); + } + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AbstractBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AbstractBatchConfiguration.java deleted file mode 100644 index 406806bd51..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AbstractBatchConfiguration.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2012-2022 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.batch.core.configuration.annotation; - -import java.util.List; -import java.util.Map; - -import javax.sql.DataSource; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.batch.core.configuration.JobRegistry; -import org.springframework.batch.core.configuration.support.MapJobRegistry; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.transaction.PlatformTransactionManager; - -/** - * Base {@code Configuration} class that provides a common structure for enabling and - * using Spring Batch. Customization is available by implementing the - * {@link BatchConfigurer} interface. - * - * @author Dave Syer - * @author Michael Minella - * @author Mahmoud Ben Hassine - * @since 2.2 - * @see EnableBatchProcessing - */ -@Configuration(proxyBeanMethods = false) -@Import(ScopeConfiguration.class) -public abstract class AbstractBatchConfiguration { - - private static final Log logger = LogFactory.getLog(AbstractBatchConfiguration.class); - - @Autowired - protected ApplicationContext context; - - private JobRegistry jobRegistry = new MapJobRegistry(); - - /** - * Establish the {@link JobRepository} for the batch execution. - * @return The instance of the {@link JobRepository}. - * @throws Exception The {@link Exception} thrown if an error occurs. - */ - @Bean - public abstract JobRepository jobRepository() throws Exception; - - /** - * Establish the {@link JobLauncher} for the batch execution. - * @return The instance of the {@link JobLauncher}. - * @throws Exception The {@link Exception} thrown if an error occurs. - */ - @Bean - public abstract JobLauncher jobLauncher() throws Exception; - - /** - * Establish the {@link JobExplorer} for the batch execution. - * @return The instance of the {@link JobExplorer}. - * @throws Exception The {@link Exception} thrown if an error occurs. - */ - @Bean - public abstract JobExplorer jobExplorer() throws Exception; - - /** - * Establish the {@link JobRegistry} for the batch execution. - * @return The instance of the {@link JobRegistry}. - * @throws Exception The {@link Exception} thrown if an error occurs. - */ - @Bean - public JobRegistry jobRegistry() throws Exception { - return this.jobRegistry; - } - - /** - * If a {@link BatchConfigurer} exists, return it. Otherwise, create a - * {@link DefaultBatchConfigurer}. If more than one configurer is present, an - * {@link IllegalStateException} is thrown. - * @return The {@link BatchConfigurer} that was in the configurers collection or the - * default one created. - */ - protected BatchConfigurer getOrCreateConfigurer() { - BatchConfigurer batchConfigurer = getConfigurer(); - if (batchConfigurer == null) { - batchConfigurer = createDefaultConfigurer(); - } - return batchConfigurer; - } - - private BatchConfigurer getConfigurer() { - Map configurers = this.context.getBeansOfType(BatchConfigurer.class); - if (configurers != null && configurers.size() > 1) { - throw new IllegalStateException( - "To use a custom BatchConfigurer the context must contain precisely one, found " - + configurers.size()); - } - if (configurers != null && configurers.size() == 1) { - return configurers.entrySet().iterator().next().getValue(); - } - return null; - } - - private BatchConfigurer createDefaultConfigurer() { - DataSource dataSource = getDataSource(); - DefaultBatchConfigurer configurer = new DefaultBatchConfigurer(dataSource); - configurer.initialize(); - return configurer; - } - - private DataSource getDataSource() { - Map dataSources = this.context.getBeansOfType(DataSource.class); - if (dataSources == null || (dataSources != null && dataSources.isEmpty())) { - throw new IllegalStateException("To use the default BatchConfigurer, the application context must" - + " contain at least one data source but none was found."); - } - if (dataSources != null && dataSources.size() > 1) { - logger.info("Multiple data sources are defined in the application context. The data source to" - + " use in the default BatchConfigurer will be the one selected by Spring according" - + " to the rules of getting the primary bean from the application context."); - return this.context.getBean(DataSource.class); - } - return dataSources.entrySet().iterator().next().getValue(); - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AutomaticJobRegistrarBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AutomaticJobRegistrarBeanPostProcessor.java new file mode 100644 index 0000000000..446e4474e1 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AutomaticJobRegistrarBeanPostProcessor.java @@ -0,0 +1,59 @@ +/* + * Copyright 2022 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.batch.core.configuration.annotation; + +import java.util.Iterator; + +import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.configuration.support.ApplicationContextFactory; +import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; +import org.springframework.batch.core.configuration.support.DefaultJobLoader; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +/** + * Post processor that configures the {@link AutomaticJobRegistrar} registered by + * {@link BatchRegistrar} with required properties. + * + * @author Mahmoud Ben Hassine + * @since 5.0 + */ +class AutomaticJobRegistrarBeanPostProcessor implements BeanFactoryPostProcessor, BeanPostProcessor { + + private ConfigurableListableBeanFactory beanFactory; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof AutomaticJobRegistrar) { + AutomaticJobRegistrar automaticJobRegistrar = (AutomaticJobRegistrar) bean; + automaticJobRegistrar.setJobLoader(new DefaultJobLoader(this.beanFactory.getBean(JobRegistry.class))); + for (ApplicationContextFactory factory : this.beanFactory.getBeansOfType(ApplicationContextFactory.class) + .values()) { + automaticJobRegistrar.addApplicationContextFactory(factory); + } + return automaticJobRegistrar; + } + return bean; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchConfigurationSelector.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchConfigurationSelector.java deleted file mode 100644 index 3c4519d8d6..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchConfigurationSelector.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2012-2022 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.batch.core.configuration.annotation; - -import org.springframework.context.annotation.ImportSelector; -import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.util.Assert; - -/** - * Base {@code Configuration} class that provides common structure for enabling and using - * Spring Batch. Customization is available by implementing the {@link BatchConfigurer} - * interface. - * - * @author Dave Syer - * @since 2.2 - * @see EnableBatchProcessing - */ -public class BatchConfigurationSelector implements ImportSelector { - - @Override - public String[] selectImports(AnnotationMetadata importingClassMetadata) { - Class annotationType = EnableBatchProcessing.class; - AnnotationAttributes attributes = AnnotationAttributes - .fromMap(importingClassMetadata.getAnnotationAttributes(annotationType.getName(), false)); - Assert.notNull(attributes, String.format("@%s is not present on importing class '%s' as expected", - annotationType.getSimpleName(), importingClassMetadata.getClassName())); - - String[] imports; - if (attributes.containsKey("modular") && attributes.getBoolean("modular")) { - imports = new String[] { ModularBatchConfiguration.class.getName() }; - } - else { - imports = new String[] { SimpleBatchConfiguration.class.getName() }; - } - - return imports; - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchConfigurer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchConfigurer.java deleted file mode 100644 index 083a482db6..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchConfigurer.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2012-2022 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.batch.core.configuration.annotation; - -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.transaction.PlatformTransactionManager; - -/** - * Strategy interface that users can provide as a factory for custom components needed by - * a Batch system. - * - * @author Dave Syer - * @author Mahmoud Ben Hassine - * - */ -public interface BatchConfigurer { - - /** - * @return The {@link JobRepository}. - * @throws Exception The {@link Exception} thrown if an error occurs. - */ - JobRepository getJobRepository() throws Exception; - - /** - * @return The {@link JobLauncher}. - * @throws Exception The {@link Exception} thrown if an error occurs. - */ - JobLauncher getJobLauncher() throws Exception; - - /** - * @return The {@link JobExplorer}. - * @throws Exception The {@link Exception} thrown if an error occurs. - */ - JobExplorer getJobExplorer() throws Exception; - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java new file mode 100644 index 0000000000..7a4635372f --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java @@ -0,0 +1,227 @@ +/* + * Copyright 2022 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.batch.core.configuration.annotation; + +import java.nio.charset.Charset; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; +import org.springframework.batch.core.configuration.support.DefaultJobLoader; +import org.springframework.batch.core.configuration.support.MapJobRegistry; +import org.springframework.batch.core.explore.support.JobExplorerFactoryBean; +import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher; +import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + * Base registrar that provides common infrastrucutre beans for enabling and using Spring + * Batch in a declarative way through {@link EnableBatchProcessing}. + * + * @author Mahmoud Ben Hassine + * @since 5.0 + * @see EnableBatchProcessing + */ +class BatchRegistrar implements ImportBeanDefinitionRegistrar { + + private static final Log LOGGER = LogFactory.getLog(BatchRegistrar.class); + + private static final String MISSING_BEAN_ERROR_MESSAGE = "Unable to find bean '%s' for attribute %s of annotation %s on class %s"; + + private static final String MISSING_ANNOTATION_ERROR_MESSAGE = "EnableBatchProcessing is not present on importing class '%s' as expected"; + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + validateState(importingClassMetadata); + EnableBatchProcessing batchAnnotation = importingClassMetadata.getAnnotations().get(EnableBatchProcessing.class) + .synthesize(); + String importingClassName = importingClassMetadata.getClassName(); + registerJobRepository(registry, batchAnnotation, importingClassName); + registerJobExplorer(registry, batchAnnotation, importingClassName); + registerJobLauncher(registry, batchAnnotation, importingClassName); + registerJobRegistry(registry); + registerAutomaticJobRegistrar(registry, batchAnnotation); + } + + private void validateState(AnnotationMetadata importingClassMetadata) { + if (!importingClassMetadata.isAnnotated(EnableBatchProcessing.class.getName())) { + String className = importingClassMetadata.getClassName(); + String errorMessage = String.format(MISSING_ANNOTATION_ERROR_MESSAGE, className); + throw new IllegalStateException(errorMessage); + } + } + + private void registerJobRepository(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation, + String importingClassName) { + if (registry.containsBeanDefinition("jobRepository")) { + LOGGER.info("Bean jobRepository already defined in the application context, skipping" + + " the registration of a jobRepository"); + return; + } + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder + .genericBeanDefinition(JobRepositoryFactoryBean.class); + + // set mandatory properties + String dataSourceRef = batchAnnotation.dataSourceRef(); + if (!registry.containsBeanDefinition(dataSourceRef)) { + String errorMessage = String.format(MISSING_BEAN_ERROR_MESSAGE, dataSourceRef, "dataSourceRef", + batchAnnotation, importingClassName); + throw new IllegalStateException(errorMessage); + } + else { + beanDefinitionBuilder.addPropertyReference("dataSource", dataSourceRef); + } + + String transactionManagerRef = batchAnnotation.transactionManagerRef(); + if (!registry.containsBeanDefinition(transactionManagerRef)) { + String errorMessage = String.format(MISSING_BEAN_ERROR_MESSAGE, transactionManagerRef, + "transactionManagerRef", batchAnnotation, importingClassName); + throw new IllegalStateException(errorMessage); + } + else { + beanDefinitionBuilder.addPropertyReference("transactionManager", transactionManagerRef); + } + + // set optional properties + String executionContextSerializerRef = batchAnnotation.executionContextSerializerRef(); + if (registry.containsBeanDefinition(executionContextSerializerRef)) { + beanDefinitionBuilder.addPropertyReference("serializer", executionContextSerializerRef); + } + + String lobHandlerRef = batchAnnotation.lobHandlerRef(); + if (registry.containsBeanDefinition(lobHandlerRef)) { + beanDefinitionBuilder.addPropertyReference("lobHandler", lobHandlerRef); + } + + String incrementerFactoryRef = batchAnnotation.incrementerFactoryRef(); + if (registry.containsBeanDefinition(incrementerFactoryRef)) { + beanDefinitionBuilder.addPropertyReference("incrementerFactory", incrementerFactoryRef); + } + + String charset = batchAnnotation.charset(); + if (charset != null) { + beanDefinitionBuilder.addPropertyValue("charset", Charset.forName(charset)); + } + + String tablePrefix = batchAnnotation.tablePrefix(); + if (tablePrefix != null) { + beanDefinitionBuilder.addPropertyValue("tablePrefix", tablePrefix); + } + + beanDefinitionBuilder.addPropertyValue("maxVarCharLength", batchAnnotation.maxVarCharLength()); + beanDefinitionBuilder.addPropertyValue("clobType", batchAnnotation.clobType()); + registry.registerBeanDefinition("jobRepository", beanDefinitionBuilder.getBeanDefinition()); + } + + private void registerJobExplorer(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation, + String importingClassName) { + if (registry.containsBeanDefinition("jobExplorer")) { + LOGGER.info("Bean jobExplorer already defined in the application context, skipping" + + " the registration of a jobExplorer"); + return; + } + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder + .genericBeanDefinition(JobExplorerFactoryBean.class); + + // set mandatory properties + String dataSourceRef = batchAnnotation.dataSourceRef(); + if (!registry.containsBeanDefinition(dataSourceRef)) { + String errorMessage = String.format(MISSING_BEAN_ERROR_MESSAGE, dataSourceRef, "dataSourceRef", + batchAnnotation, importingClassName); + throw new IllegalStateException(errorMessage); + } + else { + beanDefinitionBuilder.addPropertyReference("dataSource", dataSourceRef); + } + + // set optional properties + String executionContextSerializerRef = batchAnnotation.executionContextSerializerRef(); + if (registry.containsBeanDefinition(executionContextSerializerRef)) { + beanDefinitionBuilder.addPropertyReference("serializer", executionContextSerializerRef); + } + + String lobHandlerRef = batchAnnotation.lobHandlerRef(); + if (registry.containsBeanDefinition(lobHandlerRef)) { + beanDefinitionBuilder.addPropertyReference("lobHandler", lobHandlerRef); + } + + String charset = batchAnnotation.charset(); + if (charset != null) { + beanDefinitionBuilder.addPropertyValue("charset", Charset.forName(charset)); + } + + String tablePrefix = batchAnnotation.tablePrefix(); + if (tablePrefix != null) { + beanDefinitionBuilder.addPropertyValue("tablePrefix", tablePrefix); + } + registry.registerBeanDefinition("jobExplorer", beanDefinitionBuilder.getBeanDefinition()); + } + + private void registerJobLauncher(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation, + String importingClassName) { + if (registry.containsBeanDefinition("jobLauncher")) { + LOGGER.info("Bean jobLauncher already defined in the application context, skipping" + + " the registration of a jobLauncher"); + return; + } + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder + .genericBeanDefinition(TaskExecutorJobLauncher.class); + // set mandatory properties + beanDefinitionBuilder.addPropertyReference("jobRepository", "jobRepository"); + + // set optional properties + String taskExecutorRef = batchAnnotation.taskExecutorRef(); + if (registry.containsBeanDefinition(taskExecutorRef)) { + beanDefinitionBuilder.addPropertyReference("taskExecutor", taskExecutorRef); + } + registry.registerBeanDefinition("jobLauncher", beanDefinitionBuilder.getBeanDefinition()); + } + + private void registerJobRegistry(BeanDefinitionRegistry registry) { + if (registry.containsBeanDefinition("jobRegistry")) { + LOGGER.info("Bean jobRegistry already defined in the application context, skipping" + + " the registration of a jobRegistry"); + return; + } + BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(MapJobRegistry.class) + .getBeanDefinition(); + registry.registerBeanDefinition("jobRegistry", beanDefinition); + } + + private void registerAutomaticJobRegistrar(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { + if (!batchAnnotation.modular()) { + return; + } + if (registry.containsBeanDefinition("jobRegistrar")) { + LOGGER.info("Bean jobRegistrar already defined in the application context, skipping" + + " the registration of a jobRegistrar"); + return; + } + BeanDefinition jobLoaderBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(DefaultJobLoader.class) + .addPropertyReference("jobRegistry", "jobRegistry").getBeanDefinition(); + registry.registerBeanDefinition("jobLoader", jobLoaderBeanDefinition); + BeanDefinition jobRegistrarBeanDefinition = BeanDefinitionBuilder + .genericBeanDefinition(AutomaticJobRegistrar.class).addPropertyReference("jobLoader", "jobLoader") + .getBeanDefinition(); + registry.registerBeanDefinition("jobRegistrar", jobRegistrarBeanDefinition); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/DefaultBatchConfigurer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/DefaultBatchConfigurer.java deleted file mode 100644 index f98eb5fa20..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/DefaultBatchConfigurer.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2012-2022 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.batch.core.configuration.annotation; - -import jakarta.annotation.PostConstruct; -import javax.sql.DataSource; - -import org.springframework.batch.core.configuration.BatchConfigurationException; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.explore.support.JobExplorerFactoryBean; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher; -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.jdbc.support.JdbcTransactionManager; -import org.springframework.stereotype.Component; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.util.Assert; - -/** - * Default implementation of the {@link BatchConfigurer}. - */ -@Component -public class DefaultBatchConfigurer implements BatchConfigurer, InitializingBean { - - private DataSource dataSource; - - private PlatformTransactionManager transactionManager; - - private JobRepository jobRepository; - - private JobLauncher jobLauncher; - - private JobExplorer jobExplorer; - - /** - * Create a new {@link DefaultBatchConfigurer} with the passed datasource. This - * constructor configures a default {@link JdbcTransactionManager}. - * @param dataSource to use for the job repository and job explorer - */ - public DefaultBatchConfigurer(DataSource dataSource) { - this(dataSource, new JdbcTransactionManager(dataSource)); - } - - /** - * Create a new {@link DefaultBatchConfigurer} with the given datasource and - * transaction manager. - * @param dataSource The data source to use for the job repository and job explorer. - * @param transactionManager The transaction manager to use for the job repository. - */ - public DefaultBatchConfigurer(DataSource dataSource, PlatformTransactionManager transactionManager) { - Assert.notNull(dataSource, "DataSource must not be null"); - Assert.notNull(transactionManager, "transactionManager must not be null"); - this.dataSource = dataSource; - this.transactionManager = transactionManager; - initialize(); - } - - /** - * Sets the dataSource. - * @param dataSource The data source to use. Must not be {@code null}. - */ - public void setDataSource(DataSource dataSource) { - Assert.notNull(dataSource, "DataSource must not be null"); - this.dataSource = dataSource; - } - - /** - * @return The {@link DataSource} used by the {@link DefaultBatchConfigurer}. - */ - public DataSource getDataSource() { - return this.dataSource; - } - - @Override - public JobRepository getJobRepository() { - return this.jobRepository; - } - - @Override - public JobLauncher getJobLauncher() { - return this.jobLauncher; - } - - @Override - public JobExplorer getJobExplorer() { - return this.jobExplorer; - } - - public PlatformTransactionManager getTransactionManager() { - return this.transactionManager; - } - - /** - * Set the transaction manager. - * @param transactionManager the transaction manager to use. Must not be {@code null}. - */ - public void setTransactionManager(PlatformTransactionManager transactionManager) { - Assert.notNull(transactionManager, "TransactionManager must not be null"); - this.transactionManager = transactionManager; - } - - @Override - public void afterPropertiesSet() throws Exception { - initialize(); - } - - /** - * Initialize the {@link DefaultBatchConfigurer} with the {@link JobRepository}, - * {@link JobExplorer}, and {@link JobLauncher}. - */ - @PostConstruct - public void initialize() { - try { - this.jobRepository = createJobRepository(); - this.jobExplorer = createJobExplorer(); - this.jobLauncher = createJobLauncher(); - } - catch (Exception e) { - throw new BatchConfigurationException(e); - } - } - - /** - * @return An instance of {@link JobLauncher}. - * @throws Exception The {@link Exception} that is thrown if an error occurs while - * creating the {@link JobLauncher}. - */ - protected JobLauncher createJobLauncher() throws Exception { - TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher(); - jobLauncher.setJobRepository(this.jobRepository); - jobLauncher.afterPropertiesSet(); - return jobLauncher; - } - - /** - * @return An instance of {@link JobRepository}. - * @throws Exception The {@link Exception} that is thrown if an error occurs while - * creating the {@link JobRepository}. - */ - protected JobRepository createJobRepository() throws Exception { - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); - factory.setDataSource(getDataSource()); - factory.setTransactionManager(getTransactionManager()); - factory.afterPropertiesSet(); - return factory.getObject(); - } - - /** - * @return An instance of {@link JobExplorer}. - * @throws Exception The {@link Exception} that is thrown if an error occurs while - * creating the {@link JobExplorer}. - */ - protected JobExplorer createJobExplorer() throws Exception { - JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean(); - jobExplorerFactoryBean.setDataSource(getDataSource()); - jobExplorerFactoryBean.afterPropertiesSet(); - return jobExplorerFactoryBean.getObject(); - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java index 16b1c9ec76..3d7b0350dd 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java @@ -20,14 +20,18 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.sql.Types; + import javax.sql.DataSource; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.support.ApplicationContextFactory; import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; +import org.springframework.batch.core.configuration.support.ScopeConfiguration; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao; import org.springframework.context.annotation.Import; /** @@ -63,31 +67,10 @@ * } * * - * You should provide a {@link DataSource} as a bean in the context or else implement - * {@link BatchConfigurer} in the configuration class itself -- for example: - * - *
- * @Configuration
- * @EnableBatchProcessing
- * public class AppConfig extends DefaultBatchConfigurer {
- *
- *    @Bean
- *    public Job job() {
- *       ...
- *    }
- *
- *    @Override
- *    protected JobRepository createJobRepository() {
- *       ...
- *    }
- *
- *  ...
- *
- * }
- * 
- * - * If multiple {@link javax.sql.DataSource} instances are defined in the context, the - * primary autowire candidate is used. Otherwise, an exception is thrown. + * This annotation configures JDBC-based Batch infrastrcuture beans, so you must provide a + * {@link DataSource} and a + * {@link org.springframework.transaction.PlatformTransactionManager} as a beans in the + * application context. * * Note that only one of your configuration classes needs to have the * @EnableBatchProcessing annotation. Once you have an @@ -110,33 +93,6 @@ * {@link org.springframework.batch.core.explore.support.SimpleJobExplorer}) * * - * The transaction manager provided by this annotation is of type - * {@link org.springframework.jdbc.support.JdbcTransactionManager} and is configured with - * the {@link javax.sql.DataSource} provided within the context. - * - * To use a custom transaction manager, you should provide a custom - * {@link BatchConfigurer} -- for example: - * - *
- * @Configuration
- * @EnableBatchProcessing
- * public class AppConfig extends DefaultBatchConfigurer {
- *
- *    @Bean
- *    public Job job() {
- *       ...
- *    }
- *
- *    @Override
- *    public PlatformTransactionManager getTransactionManager() {
- *       return new MyTransactionManager();
- *    }
- *
- *  ...
- *
- * }
- * 
- * * If the configuration is specified as modular=true, the context also * contains an {@link AutomaticJobRegistrar}. The job registrar is useful for modularizing * your configuration if there are multiple jobs. It works by creating separate child @@ -166,8 +122,8 @@ * * * Note that a modular parent context, in general, should not itself contain - * @Bean definitions for job, especially if a {@link BatchConfigurer} is provided, - * because cyclic configuration dependencies are likely to develop. + * @Bean definitions for job, because cyclic configuration dependencies are likely to + * develop. * *

* For reference, compare the first example shown earlier to the following Spring XML @@ -181,6 +137,7 @@ * * * + * * * @@ -197,7 +154,7 @@ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented -@Import(BatchConfigurationSelector.class) +@Import({ BatchRegistrar.class, ScopeConfiguration.class, AutomaticJobRegistrarBeanPostProcessor.class }) public @interface EnableBatchProcessing { /** @@ -210,4 +167,68 @@ */ boolean modular() default false; + /** + * Set the data source to use in the job repository and job explorer. + * @return the bean name of the data source to use. Default to {@literal dataSource}. + */ + String dataSourceRef() default "dataSource"; + + /** + * Set the transaction manager to use in the job repository. + * @return the bean name of the transaction manager to use. Defaults to + * {@literal transactionManager} + */ + String transactionManagerRef() default "transactionManager"; + + /** + * Set the execution context serializer to use in the job repository and job explorer. + * @return the bean name of the execution context serializer to use. Default to + * {@literal executionContextSerializer}. + */ + String executionContextSerializerRef() default "executionContextSerializer"; + + /** + * The charset to use in the job repository and job explorer + * @return the charset to use. Defaults to {@literal UTF-8}. + */ + String charset() default "UTF-8"; + + /** + * The Batch tables prefix. Defaults to {@literal "BATCH_"}. + * @return the Batch table prefix + */ + String tablePrefix() default AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX; + + /** + * The maximum lenght of exit messages in the database. + * @return the maximum lenght of exit messages in the database + */ + int maxVarCharLength() default AbstractJdbcBatchMetadataDao.DEFAULT_EXIT_MESSAGE_LENGTH; + + /** + * The incrementer factory to use in various DAOs. + * @return the bean name of the incrementer factory to use. Defaults to + * {@literal incrementerFactory}. + */ + String incrementerFactoryRef() default "incrementerFactory"; + + /** + * The large object handler to use in job repository and job explorer. + * @return the bean name of the lob handler to use. Defaults to {@literal lobHandler}. + */ + String lobHandlerRef() default "lobHandler"; + + /** + * The type of large objects. + * @return the type of large objects. + */ + int clobType() default Types.CLOB; + + /** + * Set the task executor to use in the job launcher. + * @return the bean name of the task executor to use. Defaults to + * {@literal taskExecutor} + */ + String taskExecutorRef() default "taskExecutor"; + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/ModularBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/ModularBatchConfiguration.java deleted file mode 100644 index 4cc0f98ea5..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/ModularBatchConfiguration.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2012-2022 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.batch.core.configuration.annotation; - -import java.util.Collection; - -import org.springframework.batch.core.configuration.support.ApplicationContextFactory; -import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; -import org.springframework.batch.core.configuration.support.DefaultJobLoader; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.transaction.PlatformTransactionManager; - -/** - * Base {@code Configuration} class providing common structure for enabling and using - * Spring Batch. Customization is available by implementing the {@link BatchConfigurer} - * interface. - * - * @author Dave Syer - * @author Mahmoud Ben Hassine - * @since 2.2 - * @see EnableBatchProcessing - */ -@Configuration(proxyBeanMethods = false) -public class ModularBatchConfiguration extends SimpleBatchConfiguration { - - private AutomaticJobRegistrar registrar = new AutomaticJobRegistrar(); - - /** - * Creates a {@link AutomaticJobRegistrar} bean. - * @return a new instance of {@link AutomaticJobRegistrar}. - * @throws Exception if an error occurs. - */ - @Bean - public AutomaticJobRegistrar jobRegistrar() throws Exception { - registrar.setJobLoader(new DefaultJobLoader(jobRegistry())); - for (ApplicationContextFactory factory : context.getBeansOfType(ApplicationContextFactory.class).values()) { - registrar.addApplicationContextFactory(factory); - } - return registrar; - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/SimpleBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/SimpleBatchConfiguration.java deleted file mode 100644 index 5041477e20..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/SimpleBatchConfiguration.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2012-2022 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.batch.core.configuration.annotation; - -import java.util.Collection; - -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.transaction.PlatformTransactionManager; - -/** - * Base {@code Configuration} class to provide a common structure for enabling and using - * Spring Batch. Customization is available by implementing the {@link BatchConfigurer} - * interface. - * - * @author Dave Syer - * @author Mahmoud Ben Hassine - * @since 2.2 - * @see EnableBatchProcessing - */ -@Configuration(proxyBeanMethods = false) -public class SimpleBatchConfiguration extends AbstractBatchConfiguration { - - @Override - public JobRepository jobRepository() throws Exception { - return getOrCreateConfigurer().getJobRepository(); - } - - @Override - public JobLauncher jobLauncher() throws Exception { - return getOrCreateConfigurer().getJobLauncher(); - } - - @Override - public JobExplorer jobExplorer() throws Exception { - return getOrCreateConfigurer().getJobExplorer(); - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractBatchConfiguration.java new file mode 100644 index 0000000000..2ff8fa406b --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractBatchConfiguration.java @@ -0,0 +1,346 @@ +/* + * Copyright 2012-2022 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.batch.core.configuration.support; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.sql.Types; + +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.batch.core.configuration.BatchConfigurationException; +import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.explore.support.JobExplorerFactoryBean; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher; +import org.springframework.batch.core.repository.ExecutionContextSerializer; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao; +import org.springframework.batch.core.repository.dao.Jackson2ExecutionContextStringSerializer; +import org.springframework.batch.core.repository.dao.JdbcExecutionContextDao; +import org.springframework.batch.core.repository.dao.JdbcJobExecutionDao; +import org.springframework.batch.core.repository.dao.JdbcStepExecutionDao; +import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.item.database.support.DataFieldMaxValueIncrementerFactory; +import org.springframework.batch.item.database.support.DefaultDataFieldMaxValueIncrementerFactory; +import org.springframework.batch.support.DatabaseType; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.MetaDataAccessException; +import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; +import org.springframework.jdbc.support.lob.DefaultLobHandler; +import org.springframework.jdbc.support.lob.LobHandler; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Isolation; + +/** + * Base {@link Configuration} class that provides common JDBC-based infrastructure beans + * for enabling and using Spring Batch. + * + * This configuration class configures and registers the following beans in the + * application context: + * + *

    + *
  • a {@link JobRepository} named "jobRepository"
  • + *
  • a {@link JobExplorer} named "jobExplorer"
  • + *
  • a {@link JobLauncher} named "jobLauncher"
  • + *
  • a {@link JobRegistry} named "jobRegistry"
  • + *
  • a {@link org.springframework.batch.core.scope.StepScope} named "stepScope"
  • + *
  • a {@link org.springframework.batch.core.scope.JobScope} named "jobScope"
  • + *
+ * + * Customization is possible by extending the class and overriding getters. + * + * A typical usage of this class is as follows: + * + *
+ * @Configuration
+ * public class MyJobConfiguration extends AbstractBatchConfiguration {
+ *
+ *    @Bean
+ *    public Job job(JobRepository jobRepository) {
+ *       return new JobBuilder("myJob", jobRepository)
+ *              // define job flow as needed
+ *              .build();
+ *    }
+ *
+ * }
+ * 
+ * + * @author Dave Syer + * @author Michael Minella + * @author Mahmoud Ben Hassine + * @since 2.2 + */ +@Configuration(proxyBeanMethods = false) +@Import(ScopeConfiguration.class) +public class AbstractBatchConfiguration implements ApplicationContextAware { + + private static final Log LOGGER = LogFactory.getLog(AbstractBatchConfiguration.class); + + @Autowired + protected ApplicationContext applicationContext; + + private final JobRegistry jobRegistry = new MapJobRegistry(); + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Bean + public JobRepository jobRepository() throws BatchConfigurationException { + JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean(); + try { + jobRepositoryFactoryBean.setDataSource(getDataSource()); + jobRepositoryFactoryBean.setTransactionManager(getTransactionManager()); + jobRepositoryFactoryBean.setDatabaseType(getDatabaseType()); + jobRepositoryFactoryBean.setIncrementerFactory(getIncrementerFactory()); + jobRepositoryFactoryBean.setClobType(getClobType()); + jobRepositoryFactoryBean.setTablePrefix(getTablePrefix()); + jobRepositoryFactoryBean.setSerializer(getExecutionContextSerializer()); + jobRepositoryFactoryBean.setJdbcOperations(getJdbcOperations()); + jobRepositoryFactoryBean.setLobHandler(getLobHandler()); + jobRepositoryFactoryBean.setCharset(getCharset()); + jobRepositoryFactoryBean.setMaxVarCharLength(getMaxVarCharLength()); + jobRepositoryFactoryBean.setIsolationLevelForCreate(getIsolationLevelForCreate()); + jobRepositoryFactoryBean.setValidateTransactionState(getValidateTransactionState()); + jobRepositoryFactoryBean.afterPropertiesSet(); + return jobRepositoryFactoryBean.getObject(); + } + catch (Exception e) { + throw new BatchConfigurationException("Unable to configure the default job repository", e); + } + } + + @Bean + public JobLauncher jobLauncher() throws BatchConfigurationException { + TaskExecutorJobLauncher taskExecutorJobLauncher = new TaskExecutorJobLauncher(); + taskExecutorJobLauncher.setJobRepository(jobRepository()); + taskExecutorJobLauncher.setTaskExecutor(getTaskExector()); + try { + taskExecutorJobLauncher.afterPropertiesSet(); + return taskExecutorJobLauncher; + } + catch (Exception e) { + throw new BatchConfigurationException("Unable to configure the default job launcher", e); + } + } + + @Bean + public JobExplorer jobExplorer() throws BatchConfigurationException { + JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean(); + jobExplorerFactoryBean.setDataSource(getDataSource()); + jobExplorerFactoryBean.setJdbcOperations(getJdbcOperations()); + jobExplorerFactoryBean.setCharset(getCharset()); + jobExplorerFactoryBean.setTablePrefix(getTablePrefix()); + jobExplorerFactoryBean.setLobHandler(getLobHandler()); + jobExplorerFactoryBean.setSerializer(getExecutionContextSerializer()); + try { + jobExplorerFactoryBean.afterPropertiesSet(); + return jobExplorerFactoryBean.getObject(); + } + catch (Exception e) { + throw new BatchConfigurationException("Unable to configure the default job explorer", e); + } + } + + @Bean + public JobRegistry jobRegistry() throws Exception { + return this.jobRegistry; // FIXME returning a new instance here does not work + } + + /* + * Getters to customize the configuration of infrastructure beans + */ + + /** + * Return the data source to use for Batch meta-data. Defaults to the bean of type + * {@link DataSource} and named "dataSource" in the application context. + * @return The data source to use for Batch meta-data + */ + protected DataSource getDataSource() { + String errorMessage = " To use the default configuration, a data source bean named 'dataSource'" + + " should be defined in the application context but none was found. Override getDataSource()" + + " to provide the data source to use for Batch meta-data."; + if (this.applicationContext.getBeansOfType(DataSource.class).isEmpty()) { + throw new BatchConfigurationException( + "Unable to find a DataSource bean in the applicaion context." + errorMessage); + } + else { + if (!this.applicationContext.containsBean("dataSource")) { + throw new BatchConfigurationException(errorMessage); + } + } + return this.applicationContext.getBean("dataSource", DataSource.class); + } + + /** + * Return the transaction manager to use for the job repository. Defaults to the bean + * of type {@link PlatformTransactionManager} and named "transactionManager" in the + * application context. + * @return The transaction manager to use for the job repository + */ + protected PlatformTransactionManager getTransactionManager() { + String errorMessage = " To use the default configuration, a transaction manager bean named 'transactionManager'" + + " should be defined in the application context but none was found. Override getTransactionManager()" + + " to provide the transaction manager to use for the job repository."; + if (this.applicationContext.getBeansOfType(PlatformTransactionManager.class).isEmpty()) { + throw new BatchConfigurationException( + "Unable to find a PlatformTransactionManager bean in the applicaion context." + errorMessage); + } + else { + if (!this.applicationContext.containsBean("transactionManager")) { + throw new BatchConfigurationException(errorMessage); + } + } + return this.applicationContext.getBean("transactionManager", PlatformTransactionManager.class); + } + + /** + * Return the value of the {@code validateTransactionState} parameter. Defaults to + * {@code true}. + * @return true if the transaction state should be validated, false otherwise + */ + protected boolean getValidateTransactionState() { + return true; + } + + /** + * Return the transaction isolation level when creating job executions. Defaults to + * {@link Isolation#SERIALIZABLE}. + * @return the transaction isolation level when creating job executions + */ + protected Isolation getIsolationLevelForCreate() { + return Isolation.SERIALIZABLE; + } + + /** + * Return the length of long string columns in database. Do not override this if you + * haven't modified the schema. Note this value will be used for the exit message in + * both {@link JdbcJobExecutionDao} and {@link JdbcStepExecutionDao} and also the + * short version of the execution context in {@link JdbcExecutionContextDao} . For + * databases with multi-byte character sets this number can be smaller (by up to a + * factor of 2 for 2-byte characters) than the declaration of the column length in the + * DDL for the tables. Defaults to + * {@link AbstractJdbcBatchMetadataDao#DEFAULT_EXIT_MESSAGE_LENGTH} + */ + protected int getMaxVarCharLength() { + return AbstractJdbcBatchMetadataDao.DEFAULT_EXIT_MESSAGE_LENGTH; + } + + /** + * Return the prefix of Batch meta-data tables. Defaults to + * {@link AbstractJdbcBatchMetadataDao#DEFAULT_TABLE_PREFIX}. + * @return the prefix of meta-data tables + */ + protected String getTablePrefix() { + return AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX; + } + + /** + * Return the {@link Charset} to use when serializing/deserializing the execution + * context. Defaults to "UTF-8". + * @return the charset to use when serializing/deserializing the execution context + */ + protected Charset getCharset() { + return StandardCharsets.UTF_8; + } + + /** + * A special handler for large objects. The default is usually fine, except for some + * (usually older) versions of Oracle. + * @return the {@link LobHandler} to use + * + */ + protected LobHandler getLobHandler() { + return new DefaultLobHandler(); + } + + /** + * Return the {@link JdbcOperations}. If this property is not overridden, a new + * {@link JdbcTemplate} will be created for the configured data source by default. + * @return the {@link JdbcOperations} to use + */ + protected JdbcOperations getJdbcOperations() { + return new JdbcTemplate(getDataSource()); + } + + /** + * A custom implementation of the {@link ExecutionContextSerializer}. The default, if + * not injected, is the {@link Jackson2ExecutionContextStringSerializer}. + * @return the serializer to use to serialize/deserialize the execution context + */ + protected ExecutionContextSerializer getExecutionContextSerializer() { + return new Jackson2ExecutionContextStringSerializer(); + } + + /** + * Return the value from {@link java.sql.Types} class to indicate the type to use for + * a CLOB + * @return the value from {@link java.sql.Types} class to indicate the type to use for + * a CLOB + */ + protected int getClobType() { + return Types.CLOB; + } + + /** + * Return the factory for creating {@link DataFieldMaxValueIncrementer} + * implementations used to increment entity IDs in meta-data tables. + * @return the factory for creating {@link DataFieldMaxValueIncrementer} + * implementations. + */ + protected DataFieldMaxValueIncrementerFactory getIncrementerFactory() { + return new DefaultDataFieldMaxValueIncrementerFactory(getDataSource()); + } + + /** + * Return the database type. The default will be introspected from the JDBC meta-data + * of the data source. + * @return the database type + * @throws MetaDataAccessException if an error occurs when trying to get the database + * type of JDBC meta-data + * + */ + protected String getDatabaseType() throws MetaDataAccessException { + return DatabaseType.fromMetaData(getDataSource()).name(); + } + + /** + * Return the {@link TaskExecutor} to use in the the job launcher. Defaults to + * {@link SyncTaskExecutor}. + * @return the {@link TaskExecutor} to use in the the job launcher. + */ + protected TaskExecutor getTaskExector() { + return new SyncTaskExecutor(); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/ScopeConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ScopeConfiguration.java similarity index 94% rename from spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/ScopeConfiguration.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ScopeConfiguration.java index 390b7c4010..dce3368a8b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/ScopeConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ScopeConfiguration.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.configuration.annotation; +package org.springframework.batch.core.configuration.support; import org.springframework.batch.core.scope.JobScope; import org.springframework.batch.core.scope.StepScope; @@ -24,6 +24,7 @@ * {@code Configuration} class that provides {@link StepScope} and {@link JobScope}. * * @author Dave Syer + * @author Mahmoud Ben Hassine */ @Configuration(proxyBeanMethods = false) public class ScopeConfiguration { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java index e7b40b3554..1e309127c3 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java @@ -43,7 +43,7 @@ public class CoreNamespaceUtils { private static final String XML_CONFIG_STEP_SCOPE_PROCESSOR_CLASS_NAME = "org.springframework.batch.core.scope.StepScope"; - private static final String JAVA_CONFIG_SCOPE_CLASS_NAME = "org.springframework.batch.core.configuration.annotation.ScopeConfiguration"; + private static final String JAVA_CONFIG_SCOPE_CLASS_NAME = "org.springframework.batch.core.configuration.support.ScopeConfiguration"; private static final String JOB_SCOPE_PROCESSOR_BEAN_NAME = "org.springframework.batch.core.scope.internalJobScope"; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java index 4ae2569342..b9153f3af5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java @@ -82,7 +82,7 @@ public class JobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean i private ExecutionContextSerializer serializer; - private Integer lobType; + private Integer clobType; private Charset charset = StandardCharsets.UTF_8; @@ -91,7 +91,7 @@ public class JobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean i * use for a CLOB */ public void setClobType(int type) { - this.lobType = type; + this.clobType = type; } /** @@ -215,8 +215,8 @@ public void afterPropertiesSet() throws Exception { () -> "'" + databaseType + "' is an unsupported database type. The supported database types are " + StringUtils.arrayToCommaDelimitedString(incrementerFactory.getSupportedIncrementerTypes())); - if (lobType != null) { - Assert.isTrue(isValidTypes(lobType), "lobType must be a value from the java.sql.Types class"); + if (clobType != null) { + Assert.isTrue(isValidTypes(clobType), "lobType must be a value from the java.sql.Types class"); } super.afterPropertiesSet(); @@ -278,8 +278,8 @@ protected ExecutionContextDao createExecutionContextDao() throws Exception { } private int determineClobTypeToUse(String databaseType) throws Exception { - if (lobType != null) { - return lobType; + if (clobType != null) { + return clobType; } else { if (SYBASE == DatabaseType.valueOf(databaseType.toUpperCase())) { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java new file mode 100644 index 0000000000..c1d21e9c83 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java @@ -0,0 +1,187 @@ +/* + * Copyright 2022 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.batch.core.configuration.annotation; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.mockito.Mockito; + +import org.springframework.aop.Advisor; +import org.springframework.aop.framework.Advised; +import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.dao.JdbcJobInstanceDao; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.jdbc.support.JdbcTransactionManager; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.interceptor.TransactionInterceptor; + +/** + * Test class for {@link BatchRegistrar}. + * + * @author Mahmoud Ben Hassine + */ +class BatchRegistrarTests { + + @Test + @DisplayName("When no datasource is provided, then an IllegalStateException should be thrown") + void testMissingDataSource() { + Assertions.assertThrows(IllegalStateException.class, new Executable() { + @Override + public void execute() throws Throwable { + new AnnotationConfigApplicationContext(JobConfigurationWithoutDataSource.class); + } + }); + } + + @Test + @DisplayName("When no transaction manager is provided, then an IllegalStateException should be thrown") + void testMissingTransactionManager() { + Assertions.assertThrows(IllegalStateException.class, new Executable() { + @Override + public void execute() throws Throwable { + new AnnotationConfigApplicationContext(JobConfigurationWithoutTransactionManager.class); + } + }); + } + + @Test + @DisplayName("When cusotm beans are provided, then no new ones should be created") + void testConfigurationWithUserDefinedBeans() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + JobConfigurationWithUserDefinedInfrastrucutreBeans.class); + + Assertions.assertEquals(JobConfigurationWithUserDefinedInfrastrucutreBeans.jobRepository, + context.getBean(JobRepository.class)); + Assertions.assertEquals(JobConfigurationWithUserDefinedInfrastrucutreBeans.jobExplorer, + context.getBean(JobExplorer.class)); + Assertions.assertEquals(JobConfigurationWithUserDefinedInfrastrucutreBeans.jobLauncher, + context.getBean(JobLauncher.class)); + Assertions.assertEquals(JobConfigurationWithUserDefinedInfrastrucutreBeans.jobRegistry, + context.getBean(JobRegistry.class)); + } + + @Test + @DisplayName("When a datasource and a transaction manager are provided, then they should be set on the job repository") + void testDataSourceAndTransactionManagerSetup() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JobConfiguration.class); + + JobRepository jobRepository = context.getBean(JobRepository.class); + JdbcJobInstanceDao jobInstanceDao = (JdbcJobInstanceDao) ReflectionTestUtils.getField(jobRepository, + "jobInstanceDao"); + JdbcTemplate jdbcTemplate = (JdbcTemplate) ReflectionTestUtils.getField(jobInstanceDao, "jdbcTemplate"); + DataSource dataSource = (DataSource) ReflectionTestUtils.getField(jdbcTemplate, "dataSource"); + Assertions.assertEquals(context.getBean(DataSource.class), dataSource); + + // TODO assert on other DAOs + + PlatformTransactionManager transactionManager = getTransactionManagerSetOnJobRepository(jobRepository); + Assertions.assertEquals(context.getBean(JdbcTransactionManager.class), transactionManager); + } + + @Configuration + @EnableBatchProcessing + public static class JobConfigurationWithoutDataSource { + + } + + @Configuration + @EnableBatchProcessing + public static class JobConfigurationWithoutTransactionManager { + + @Bean + public DataSource dataSource() { + return Mockito.mock(DataSource.class); + } + + } + + @Configuration + @EnableBatchProcessing + public static class JobConfigurationWithUserDefinedInfrastrucutreBeans { + + public static JobRepository jobRepository = Mockito.mock(JobRepository.class); + + public static JobExplorer jobExplorer = Mockito.mock(JobExplorer.class); + + public static JobLauncher jobLauncher = Mockito.mock(JobLauncher.class); + + public static JobRegistry jobRegistry = Mockito.mock(JobRegistry.class); + + @Bean + public JobRepository jobRepository() { + return jobRepository; + } + + @Bean + public JobExplorer jobExplorer() { + return jobExplorer; + } + + @Bean + public JobLauncher jobLauncher() { + return jobLauncher; + } + + @Bean + public JobRegistry jobRegistry() { + return jobRegistry; + } + + } + + @Configuration + @EnableBatchProcessing + public static class JobConfiguration { + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL) + .addScript("/org/springframework/batch/core/schema-hsqldb.sql").generateUniqueName(true).build(); + } + + @Bean + public JdbcTransactionManager transactionManager(DataSource dataSource) { + return new JdbcTransactionManager(dataSource); + } + + } + + private PlatformTransactionManager getTransactionManagerSetOnJobRepository(JobRepository jobRepository) { + Advised target = (Advised) jobRepository; // proxy created by + // AbstractJobRepositoryFactoryBean + Advisor[] advisors = target.getAdvisors(); + for (Advisor advisor : advisors) { + if (advisor.getAdvice() instanceof TransactionInterceptor transactionInterceptor) { + return (PlatformTransactionManager) transactionInterceptor.getTransactionManager(); + } + } + return null; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/DataSourceConfiguration.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/DataSourceConfiguration.java index 708c930490..bff13187f3 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/DataSourceConfiguration.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/DataSourceConfiguration.java @@ -15,38 +15,22 @@ */ package org.springframework.batch.core.configuration.annotation; -import org.springframework.batch.core.Step; -import org.springframework.beans.factory.annotation.Autowired; +import javax.sql.DataSource; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ResourceLoader; -import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; -import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; -import org.springframework.util.ClassUtils; - -import jakarta.annotation.PostConstruct; -import javax.sql.DataSource; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.jdbc.support.JdbcTransactionManager; @Configuration public class DataSourceConfiguration { - @Autowired - private ResourceLoader resourceLoader; - - @PostConstruct - protected void initialize() { - ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); - populator.addScript( - resourceLoader.getResource(ClassUtils.addResourcePathToPackagePath(Step.class, "schema-hsqldb.sql"))); - populator.setContinueOnError(true); - DatabasePopulatorUtils.execute(populator, dataSource()); - } - @Bean public DataSource dataSource() { - return new EmbeddedDatabaseBuilder().generateUniqueName(true).build(); + return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL) + .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") + .addScript("/org/springframework/batch/core/schema-hsqldb.sql").generateUniqueName(true).build(); } @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.java index 23ff019d51..e0e32e2f41 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.java @@ -61,11 +61,6 @@ void testVanillaBatchConfiguration() throws Exception { testJob(BatchStatus.COMPLETED, 2, TestConfiguration.class); } - @Test - void testConfigurerAsConfiguration() throws Exception { - testJob(BatchStatus.COMPLETED, 1, TestConfigurer.class); - } - @Test void testConfigurerAsBean() throws Exception { testJob(BatchStatus.COMPLETED, 1, BeansConfigurer.class); @@ -76,11 +71,6 @@ void testTwoConfigurations() throws Exception { testJob("testJob", BatchStatus.COMPLETED, 2, TestConfiguration.class, AnotherConfiguration.class); } - @Test - void testTwoConfigurationsAndConfigurer() throws Exception { - testJob("testJob", BatchStatus.COMPLETED, 2, TestConfiguration.class, TestConfigurer.class); - } - @Test void testTwoConfigurationsAndBeansConfigurer() throws Exception { testJob("testJob", BatchStatus.COMPLETED, 2, TestConfiguration.class, BeansConfigurer.class); @@ -174,36 +164,6 @@ protected Step step3(JobRepository jobRepository) throws Exception { } - @Configuration - @EnableBatchProcessing - @Import(DataSourceConfiguration.class) - public static class TestConfigurer extends DefaultBatchConfigurer { - - public TestConfigurer(DataSource dataSource) { - super(dataSource); - } - - @Bean - public Job testConfigurerJob(JobRepository jobRepository) throws Exception { - SimpleJobBuilder builder = new JobBuilder("configurer", jobRepository).start(step1()); - return builder.build(); - } - - @Bean - protected Step step1() throws Exception { - AbstractStep step = new AbstractStep("step1") { - @Override - protected void doExecute(StepExecution stepExecution) throws Exception { - stepExecution.setExitStatus(ExitStatus.COMPLETED); - stepExecution.setStatus(BatchStatus.COMPLETED); - } - }; - step.setJobRepository(getJobRepository()); - return step; - } - - } - @Configuration @EnableBatchProcessing @Import(DataSourceConfiguration.class) @@ -230,12 +190,6 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon }, this.transactionManager).build(); } - @Bean - @Autowired - protected BatchConfigurer configurer(DataSource dataSource) { - return new DefaultBatchConfigurer(dataSource); - } - } @Configuration diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTests.java index bbd043477a..b8af253b89 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTests.java @@ -40,6 +40,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportResource; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationWithBatchConfigurerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationWithBatchConfigurerTests.java deleted file mode 100644 index fcd97dacc5..0000000000 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationWithBatchConfigurerTests.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2018-2022 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.batch.core.configuration.annotation; - -import javax.sql.DataSource; - -import org.junit.jupiter.api.Test; - -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.jdbc.support.JdbcTransactionManager; -import org.springframework.test.util.AopTestUtils; -import org.springframework.transaction.PlatformTransactionManager; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @author Mahmoud Ben Hassine - */ -class TransactionManagerConfigurationWithBatchConfigurerTests extends TransactionManagerConfigurationTests { - - @Test - void testConfigurationWithDataSourceAndNoTransactionManager() throws Exception { - ApplicationContext applicationContext = new AnnotationConfigApplicationContext( - BatchConfigurationWithDataSourceAndNoTransactionManager.class); - DefaultBatchConfigurer batchConfigurer = applicationContext.getBean(DefaultBatchConfigurer.class); - - PlatformTransactionManager platformTransactionManager = batchConfigurer.getTransactionManager(); - assertTrue(platformTransactionManager instanceof JdbcTransactionManager); - JdbcTransactionManager JdbcTransactionManager = AopTestUtils.getTargetObject(platformTransactionManager); - assertEquals(applicationContext.getBean(DataSource.class), JdbcTransactionManager.getDataSource()); - assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)), - platformTransactionManager); - } - - @Test - void testConfigurationWithDataSourceAndTransactionManager() throws Exception { - ApplicationContext applicationContext = new AnnotationConfigApplicationContext( - BatchConfigurationWithDataSourceAndTransactionManager.class); - DefaultBatchConfigurer batchConfigurer = applicationContext.getBean(DefaultBatchConfigurer.class); - - PlatformTransactionManager platformTransactionManager = batchConfigurer.getTransactionManager(); - assertSame(transactionManager, platformTransactionManager); - assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)), - transactionManager); - } - - @Configuration - @EnableBatchProcessing - public static class BatchConfigurationWithDataSourceAndNoTransactionManager { - - @Bean - public DataSource dataSource() { - return createDataSource(); - } - - @Bean - public BatchConfigurer batchConfigurer(DataSource dataSource) { - return new DefaultBatchConfigurer(dataSource); - } - - } - - @Configuration - @EnableBatchProcessing - public static class BatchConfigurationWithDataSourceAndTransactionManager { - - @Bean - public DataSource dataSource() { - return createDataSource(); - } - - @Bean - public PlatformTransactionManager transactionManager() { - return transactionManager; - } - - @Bean - public BatchConfigurer batchConfigurer(DataSource dataSource) { - return new DefaultBatchConfigurer(dataSource) { - @Override - public PlatformTransactionManager getTransactionManager() { - return transactionManager(); - } - }; - } - - } - -} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationWithoutBatchConfigurerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationWithoutBatchConfigurerTests.java deleted file mode 100644 index 59c99cc56e..0000000000 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationWithoutBatchConfigurerTests.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2018-2022 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.batch.core.configuration.annotation; - -import javax.sql.DataSource; - -import org.junit.jupiter.api.Test; - -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.jdbc.support.JdbcTransactionManager; -import org.springframework.transaction.PlatformTransactionManager; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @author Mahmoud Ben Hassine - */ -class TransactionManagerConfigurationWithoutBatchConfigurerTests extends TransactionManagerConfigurationTests { - - @Test - void testConfigurationWithNoDataSourceAndNoTransactionManager() { - assertThrows(BeanCreationException.class, () -> new AnnotationConfigApplicationContext( - BatchConfigurationWithNoDataSourceAndNoTransactionManager.class)); - } - - @Test - void testConfigurationWithNoDataSourceAndTransactionManager() { - assertThrows(BeanCreationException.class, () -> new AnnotationConfigApplicationContext( - BatchConfigurationWithNoDataSourceAndTransactionManager.class)); - } - - @Test - void testConfigurationWithDataSourceAndNoTransactionManager() throws Exception { - ApplicationContext applicationContext = new AnnotationConfigApplicationContext( - BatchConfigurationWithDataSourceAndNoTransactionManager.class); - PlatformTransactionManager platformTransactionManager = getTransactionManagerSetOnJobRepository( - applicationContext.getBean(JobRepository.class)); - assertTrue(platformTransactionManager instanceof JdbcTransactionManager); - JdbcTransactionManager JdbcTransactionManager = (JdbcTransactionManager) platformTransactionManager; - assertEquals(applicationContext.getBean(DataSource.class), JdbcTransactionManager.getDataSource()); - } - - @Test - void testConfigurationWithDataSourceAndOneTransactionManager() throws Exception { - ApplicationContext applicationContext = new AnnotationConfigApplicationContext( - BatchConfigurationWithDataSourceAndOneTransactionManager.class); - PlatformTransactionManager platformTransactionManager = applicationContext - .getBean(PlatformTransactionManager.class); - assertSame(transactionManager, platformTransactionManager); - // In this case, the supplied transaction manager won't be used by batch and a - // JdbcTransactionManager will be used instead. - // The user has to provide a custom BatchConfigurer. - assertTrue(getTransactionManagerSetOnJobRepository( - applicationContext.getBean(JobRepository.class)) instanceof JdbcTransactionManager); - } - - @Test - void testConfigurationWithDataSourceAndMultipleTransactionManagers() throws Exception { - ApplicationContext applicationContext = new AnnotationConfigApplicationContext( - BatchConfigurationWithDataSourceAndMultipleTransactionManagers.class); - PlatformTransactionManager platformTransactionManager = applicationContext - .getBean(PlatformTransactionManager.class); - assertSame(transactionManager2, platformTransactionManager); - // In this case, the supplied primary transaction manager won't be used by batch - // and a JdbcTransactionManager will be used instead. - // The user has to provide a custom BatchConfigurer. - assertTrue(getTransactionManagerSetOnJobRepository( - applicationContext.getBean(JobRepository.class)) instanceof JdbcTransactionManager); - } - - @Configuration - @EnableBatchProcessing - public static class BatchConfigurationWithNoDataSourceAndNoTransactionManager { - - } - - @Configuration - @EnableBatchProcessing - public static class BatchConfigurationWithNoDataSourceAndTransactionManager { - - @Bean - public PlatformTransactionManager transactionManager() { - return transactionManager; - } - - } - - @Configuration - @EnableBatchProcessing - public static class BatchConfigurationWithDataSourceAndNoTransactionManager { - - @Bean - public DataSource dataSource() { - return createDataSource(); - } - - } - - @Configuration - @EnableBatchProcessing - public static class BatchConfigurationWithDataSourceAndOneTransactionManager { - - @Bean - public DataSource dataSource() { - return createDataSource(); - } - - @Bean - public PlatformTransactionManager transactionManager() { - return transactionManager; - } - - } - - @Configuration - @EnableBatchProcessing - public static class BatchConfigurationWithDataSourceAndMultipleTransactionManagers { - - @Bean - public DataSource dataSource() { - return createDataSource(); - } - - @Bean - public PlatformTransactionManager transactionManager() { - return transactionManager; - } - - @Primary - @Bean - public PlatformTransactionManager transactionManager2() { - return transactionManager2; - } - - } - -} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AbstractBatchConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AbstractBatchConfigurationTests.java new file mode 100644 index 0000000000..acbee59e2d --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AbstractBatchConfigurationTests.java @@ -0,0 +1,148 @@ +/* + * Copyright 2022 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.batch.core.configuration.support; + +import java.util.Map; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.BatchConfigurationException; +import org.springframework.batch.core.configuration.xml.DummyJobRepository; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.jdbc.support.JdbcTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * @author Mahmoud Ben Hassine + */ +class AbstractBatchConfigurationTests { + + @Test + void testBasicConfiguration() throws Exception { + // given + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyJobConfiguration.class); + Job job = context.getBean(Job.class); + JobLauncher jobLauncher = context.getBean(JobLauncher.class); + + // when + JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + + // then + Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + } + + @Test + void testConfigurationWithoutDataSource() { + Assertions.assertThrows(BeanCreationException.class, new Executable() { + @Override + public void execute() throws Throwable { + new AnnotationConfigApplicationContext(MyJobConfigurationWithoutDataSource.class); + } + }); + } + + @Test + void testConfigurationWithoutTransactionManager() { + Assertions.assertThrows(BeanCreationException.class, new Executable() { + @Override + public void execute() throws Throwable { + new AnnotationConfigApplicationContext(MyJobConfigurationWithoutTransactionManager.class); + } + }); + } + + @Test + void testConfigurationWithCustomInfrastructureBean() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + MyJobConfigurationWithCustomInfrastructureBean.class); + Map jobRepositories = context.getBeansOfType(JobRepository.class); + Assertions.assertEquals(1, jobRepositories.size()); + JobRepository jobRepository = jobRepositories.entrySet().iterator().next().getValue(); + Assertions.assertInstanceOf(DummyJobRepository.class, jobRepository); + } + + @Configuration + static class MyJobConfigurationWithoutDataSource extends AbstractBatchConfiguration { + + } + + @Configuration + static class MyJobConfigurationWithoutTransactionManager extends AbstractBatchConfiguration { + + } + + @Configuration + static class MyJobConfiguration extends AbstractBatchConfiguration { + + @Bean + public Step myStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) { + Tasklet myTasklet = (contribution, chunkContext) -> { + System.out.println("Hello world"); + return RepeatStatus.FINISHED; + }; + return new StepBuilder("myStep", jobRepository).tasklet(myTasklet, transactionManager).build(); + } + + @Bean + public Job job(JobRepository jobRepository, Step myStep) { + + return new JobBuilder("job", jobRepository).start(myStep).build(); + } + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL) + .addScript("/org/springframework/batch/core/schema-hsqldb.sql").generateUniqueName(true).build(); + } + + @Bean + public PlatformTransactionManager transactionManager(DataSource dataSource) { + return new JdbcTransactionManager(dataSource); + } + + } + + @Configuration + static class MyJobConfigurationWithCustomInfrastructureBean extends MyJobConfiguration { + + @Bean + public JobRepository jobRepository() { + return new DummyJobRepository(); + } + + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerIntegrationTests.java index 068a678c0c..cbff4c420f 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerIntegrationTests.java @@ -18,6 +18,8 @@ import java.util.ArrayList; import java.util.List; +import javax.sql.DataSource; + import org.apache.commons.dbcp2.BasicDataSource; import org.junit.jupiter.api.Test; import test.jdbc.datasource.DataSourceInitializer; @@ -52,6 +54,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; +import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -127,6 +130,11 @@ public DataSourceInitializer dataSourceInitializer() { return dataSourceInitializer; } + @Bean + public JdbcTransactionManager transactionManager(DataSource dataSource) { + return new JdbcTransactionManager(dataSource); + } + @Bean public Job job(JobRepository jobRepository) { return new JobBuilder("job", jobRepository).start(dummyStep()).build(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/concurrent/ConcurrentTransactionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/concurrent/ConcurrentTransactionTests.java index f3da0407b4..1d089f42d0 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/concurrent/ConcurrentTransactionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/concurrent/ConcurrentTransactionTests.java @@ -21,6 +21,7 @@ import java.sql.Statement; import javax.sql.DataSource; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; @@ -29,7 +30,6 @@ import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.FlowBuilder; import org.springframework.batch.core.job.builder.JobBuilder; @@ -48,6 +48,7 @@ import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.jdbc.datasource.embedded.ConnectionProperties; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseConfigurer; @@ -67,6 +68,8 @@ * @author Michael Minella * @author Mahmoud Ben Hassine */ +// FIXME incorrect configuration of JobLauncher. This should be failing with v4. +@Disabled @SpringJUnitConfig(classes = ConcurrentTransactionTests.ConcurrentJobConfiguration.class) class ConcurrentTransactionTests { @@ -82,17 +85,13 @@ void testConcurrentLongRunningJobExecutions() throws Exception { JobExecution jobExecution = jobLauncher.run(concurrentJob, new JobParameters()); - assertEquals(jobExecution.getStatus(), BatchStatus.COMPLETED); + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); } @Configuration @EnableBatchProcessing @Import(DataSourceConfiguration.class) - public static class ConcurrentJobConfiguration extends DefaultBatchConfigurer { - - public ConcurrentJobConfiguration(DataSource dataSource, PlatformTransactionManager transactionManager) { - super(dataSource, transactionManager); - } + public static class ConcurrentJobConfiguration { @Bean public TaskExecutor taskExecutor() { @@ -100,7 +99,7 @@ public TaskExecutor taskExecutor() { } @Bean - public Flow flow(JobRepository jobRepository) { + public Flow flow(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new FlowBuilder("flow") .start(new StepBuilder("flow.step1", jobRepository).tasklet(new Tasklet() { @Nullable @@ -109,19 +108,19 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon throws Exception { return RepeatStatus.FINISHED; } - }, getTransactionManager()).build()) - .next(new StepBuilder("flow.step2", jobRepository).tasklet(new Tasklet() { + }, transactionManager).build()) + .next(new StepBuilder("flow.step2").repository(jobRepository).tasklet(new Tasklet() { @Nullable @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { return RepeatStatus.FINISHED; } - }, getTransactionManager()).build()).build(); + }, transactionManager).build()).build(); } @Bean - public Step firstStep(JobRepository jobRepository) { + public Step firstStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("firstStep", jobRepository).tasklet(new Tasklet() { @Nullable @Override @@ -129,11 +128,11 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon System.out.println(">> Beginning concurrent job test"); return RepeatStatus.FINISHED; } - }, getTransactionManager()).build(); + }, transactionManager).build(); } @Bean - public Step lastStep(JobRepository jobRepository) { + public Step lastStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("lastStep", jobRepository).tasklet(new Tasklet() { @Nullable @Override @@ -141,27 +140,31 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon System.out.println(">> Ending concurrent job test"); return RepeatStatus.FINISHED; } - }, getTransactionManager()).build(); + }, transactionManager).build(); } @Bean - public Job concurrentJob(JobRepository jobRepository) { - Flow splitFlow = new FlowBuilder("splitflow").split(new SimpleAsyncTaskExecutor()) - .add(flow(jobRepository), flow(jobRepository), flow(jobRepository), flow(jobRepository), - flow(jobRepository), flow(jobRepository), flow(jobRepository)) + public Job concurrentJob(JobRepository jobRepository, PlatformTransactionManager transactionManager, + TaskExecutor taskExecutor) { + Flow splitFlow = new FlowBuilder("splitflow").split(taskExecutor) + .add(flow(jobRepository, transactionManager), flow(jobRepository, transactionManager), + flow(jobRepository, transactionManager), flow(jobRepository, transactionManager), + flow(jobRepository, transactionManager), flow(jobRepository, transactionManager), + flow(jobRepository, transactionManager)) .build(); - return new JobBuilder("concurrentJob", jobRepository).start(firstStep(jobRepository)) + return new JobBuilder("concurrentJob", jobRepository).start(firstStep(jobRepository, transactionManager)) .next(new StepBuilder("splitFlowStep", jobRepository).flow(splitFlow).build()) - .next(lastStep(jobRepository)).build(); + .next(lastStep(jobRepository, transactionManager)).build(); } - @Override - protected JobRepository createJobRepository() throws Exception { + @Bean + public JobRepository jobRepository(DataSource dataSource, PlatformTransactionManager transactionManager) + throws Exception { JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); - factory.setDataSource(getDataSource()); + factory.setDataSource(dataSource); factory.setIsolationLevelForCreate(Isolation.READ_COMMITTED); - factory.setTransactionManager(getTransactionManager()); + factory.setTransactionManager(transactionManager); factory.afterPropertiesSet(); return factory.getObject(); } @@ -183,7 +186,7 @@ static class DataSourceConfiguration { * @return */ @Bean - DataSource dataSource() { + public DataSource dataSource() { ResourceLoader defaultResourceLoader = new DefaultResourceLoader(); EmbeddedDatabaseFactory embeddedDatabaseFactory = new EmbeddedDatabaseFactory(); embeddedDatabaseFactory.setDatabaseConfigurer(new EmbeddedDatabaseConfigurer() { diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/config/xml/JobLauncherParserTestsConfiguration.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/config/xml/JobLauncherParserTestsConfiguration.java index 1c854c2379..d525fcc251 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/config/xml/JobLauncherParserTestsConfiguration.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/config/xml/JobLauncherParserTestsConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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 @@ -18,6 +18,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.support.JdbcTransactionManager; /** * @author Gunnar Hillert @@ -35,4 +36,9 @@ public DataSource dataSource() { .addScript("/org/springframework/batch/core/schema-hsqldb.sql").generateUniqueName(true).build(); } + @Bean + public JdbcTransactionManager transactionManager(DataSource dataSource) { + return new JdbcTransactionManager(dataSource); + } + } diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilderTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilderTests.java index 17a3650f48..4da9aaf848 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilderTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilderTests.java @@ -34,6 +34,7 @@ import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.core.MessagingTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; @@ -241,6 +242,11 @@ DataSource dataSource() { .addScript("/org/springframework/batch/core/schema-hsqldb.sql").generateUniqueName(true).build(); } + @Bean + public JdbcTransactionManager transactionManager(DataSource dataSource) { + return new JdbcTransactionManager(dataSource); + } + } }