From 024769f17aba9fbe5227ffe70669255a71687401 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 Issue #3942 --- .../AbstractBatchConfiguration.java | 145 -------------- ...utomaticJobRegistrarBeanPostProcessor.java | 59 ++++++ .../BatchConfigurationSelector.java | 53 ----- .../annotation/BatchConfigurer.java | 51 ----- .../annotation/BatchRegistrar.java | 189 ++++++++++++++++++ .../annotation/DefaultBatchConfigurer.java | 175 ---------------- .../annotation/EnableBatchProcessing.java | 90 ++++----- .../annotation/ModularBatchConfiguration.java | 61 ------ .../annotation/SimpleBatchConfiguration.java | 56 ------ .../annotation/BatchRegistrarTests.java | 187 +++++++++++++++++ .../annotation/DataSourceConfiguration.java | 30 +-- .../JobBuilderConfigurationTests.java | 46 ----- .../JobScopeConfigurationTests.java | 1 + ...ConfigurationWithBatchConfigurerTests.java | 109 ---------- ...figurationWithoutBatchConfigurerTests.java | 160 --------------- .../SimpleJobExplorerIntegrationTests.java | 8 + .../ConcurrentTransactionTests.java | 54 ++--- .../JobLauncherParserTestsConfiguration.java | 8 +- ...tePartitioningManagerStepBuilderTests.java | 6 + 19 files changed, 528 insertions(+), 960 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/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 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..3f730ed574 --- /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 + */ +public 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..8929e641c5 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java @@ -0,0 +1,189 @@ +/* + * 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 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 + */ +public 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); + } + // TODO set other properties (LobHandler, TablePrefix, etc) + 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); + } + // TODO set other properties (LobHandler, TablePrefix, etc) + 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..6ec50140cd 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 @@ -63,31 +63,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 +89,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 +118,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 +133,7 @@ * * * + * * * @@ -197,7 +150,7 @@ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented -@Import(BatchConfigurationSelector.class) +@Import({ BatchRegistrar.class, ScopeConfiguration.class, AutomaticJobRegistrarBeanPostProcessor.class }) public @interface EnableBatchProcessing { /** @@ -210,4 +163,31 @@ */ 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"; + + /** + * 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/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 fcb65ea298..a668439504 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); @@ -178,36 +168,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").repository(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) @@ -234,12 +194,6 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon }).transactionManager(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/explore/support/SimpleJobExplorerIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerIntegrationTests.java index 048b791831..2b83d2023b 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").repository(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 f5e2a701a0..678222f5fa 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").repository(jobRepository).tasklet(new Tasklet() { @Nullable @@ -109,7 +108,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon throws Exception { return RepeatStatus.FINISHED; } - }).transactionManager(getTransactionManager()).build()) + }).transactionManager(transactionManager).build()) .next(new StepBuilder("flow.step2").repository(jobRepository).tasklet(new Tasklet() { @Nullable @Override @@ -117,11 +116,11 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon throws Exception { return RepeatStatus.FINISHED; } - }).transactionManager(getTransactionManager()).build()).build(); + }).transactionManager(transactionManager).build()).build(); } @Bean - public Step firstStep(JobRepository jobRepository) { + public Step firstStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("firstStep").repository(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; } - }).transactionManager(getTransactionManager()).build(); + }).transactionManager(transactionManager).build(); } @Bean - public Step lastStep(JobRepository jobRepository) { + public Step lastStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("lastStep").repository(jobRepository).tasklet(new Tasklet() { @Nullable @Override @@ -141,27 +140,32 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon System.out.println(">> Ending concurrent job test"); return RepeatStatus.FINISHED; } - }).transactionManager(getTransactionManager()).build(); + }).transactionManager(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").repository(jobRepository).start(firstStep(jobRepository)) + return new JobBuilder("concurrentJob").repository(jobRepository) + .start(firstStep(jobRepository, transactionManager)) .next(new StepBuilder("splitFlowStep").repository(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 +187,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 68221a604e..116b9ee63d 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; @@ -235,6 +236,11 @@ DataSource dataSource() { .addScript("/org/springframework/batch/core/schema-hsqldb.sql").generateUniqueName(true).build(); } + @Bean + public JdbcTransactionManager transactionManager(DataSource dataSource) { + return new JdbcTransactionManager(dataSource); + } + } }