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 index 389f392351..f1c0ce7225 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -59,7 +59,8 @@ public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } - if(this.transactionManager == null) { + if(getTransactionManager() == null) { + logger.warn("No transaction manager was provided, using a DataSourceTransactionManager"); this.transactionManager = new DataSourceTransactionManager(this.dataSource); } } @@ -96,11 +97,12 @@ public void initialize() { if(dataSource == null) { logger.warn("No datasource was provided...using a Map based JobRepository"); - if(this.transactionManager == null) { + if(getTransactionManager() == null) { + logger.warn("No transaction manager was provided, using a ResourcelessTransactionManager"); this.transactionManager = new ResourcelessTransactionManager(); } - MapJobRepositoryFactoryBean jobRepositoryFactory = new MapJobRepositoryFactoryBean(this.transactionManager); + MapJobRepositoryFactoryBean jobRepositoryFactory = new MapJobRepositoryFactoryBean(getTransactionManager()); jobRepositoryFactory.afterPropertiesSet(); this.jobRepository = jobRepositoryFactory.getObject(); @@ -128,7 +130,7 @@ protected JobLauncher createJobLauncher() throws Exception { protected JobRepository createJobRepository() throws Exception { JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); factory.setDataSource(dataSource); - factory.setTransactionManager(transactionManager); + factory.setTransactionManager(getTransactionManager()); factory.afterPropertiesSet(); return factory.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 5fa35dfb2f..c725d592a9 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 @@ -111,6 +111,37 @@ * job repository and transaction manager into every step * * + * The transaction manager provided by this annotation will be of type: + * + * + * + * In order to use a custom transaction manager, a custom {@link BatchConfigurer} should be provided. 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 then the context will also contain an * {@link AutomaticJobRegistrar}. The job registrar is useful for modularizing your configuration if there are multiple * jobs. It works by creating separate child application contexts containing job configurations and registering those diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationTests.java new file mode 100644 index 0000000000..2219488169 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2018 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 + * + * http://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.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import org.springframework.aop.Advisor; +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.Advised; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.interceptor.TransactionInterceptor; + +/** + * @author Mahmoud Ben Hassine + */ +@RunWith(MockitoJUnitRunner.class) +public abstract class TransactionManagerConfigurationTests { + + @Mock + protected static PlatformTransactionManager transactionManager; + + @Mock + protected static PlatformTransactionManager transactionManager2; + + /* + * The transaction manager set on JobRepositoryFactoryBean in DefaultBatchConfigurer.createJobRepository + * ends up in the TransactionInterceptor advise applied to the (proxied) JobRepository. + * This method extracts the advise from the proxy and returns the transaction manager. + */ + PlatformTransactionManager getTransactionManagerSetOnJobRepository(JobRepository jobRepository) throws Exception { + TargetSource targetSource = ((Advised) jobRepository).getTargetSource(); // proxy created in SimpleBatchConfiguration.createLazyProxy + Advised target = (Advised) targetSource.getTarget(); // initial proxy created in AbstractJobRepositoryFactoryBean.initializeProxy + Advisor[] advisors = target.getAdvisors(); + for (Advisor advisor : advisors) { + if (advisor.getAdvice() instanceof TransactionInterceptor) { + TransactionInterceptor transactionInterceptor = (TransactionInterceptor) advisor.getAdvice(); + return transactionInterceptor.getTransactionManager(); + } + } + return null; + } + + static DataSource createDataSource() { + return new EmbeddedDatabaseBuilder() + .addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql") + .addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql") + .build(); + } +} 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 new file mode 100644 index 0000000000..d222230387 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationWithBatchConfigurerTests.java @@ -0,0 +1,146 @@ +/* + * Copyright 2018 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 + * + * http://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.Assert; +import org.junit.Test; + +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.test.util.AopTestUtils; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * @author Mahmoud Ben Hassine + */ +public class TransactionManagerConfigurationWithBatchConfigurerTests extends TransactionManagerConfigurationTests { + + @Test + public void testConfigurationWithNoDataSourceAndNoTransactionManager() throws Exception { + ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BatchConfigurationWithNoDataSourceAndNoTransactionManager.class); + BatchConfigurer batchConfigurer = applicationContext.getBean(BatchConfigurer.class); + + PlatformTransactionManager platformTransactionManager = batchConfigurer.getTransactionManager(); + Assert.assertTrue(platformTransactionManager instanceof ResourcelessTransactionManager); + Assert.assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)), platformTransactionManager); + } + + @Test + public void testConfigurationWithNoDataSourceAndTransactionManager() throws Exception { + ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BatchConfigurationWithNoDataSourceAndTransactionManager.class); + BatchConfigurer batchConfigurer = applicationContext.getBean(BatchConfigurer.class); + + PlatformTransactionManager platformTransactionManager = batchConfigurer.getTransactionManager(); + Assert.assertSame(transactionManager, platformTransactionManager); + Assert.assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)), transactionManager); + } + + @Test + public void testConfigurationWithDataSourceAndNoTransactionManager() throws Exception { + ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BatchConfigurationWithDataSourceAndNoTransactionManager.class); + BatchConfigurer batchConfigurer = applicationContext.getBean(BatchConfigurer.class); + + PlatformTransactionManager platformTransactionManager = batchConfigurer.getTransactionManager(); + Assert.assertTrue(platformTransactionManager instanceof DataSourceTransactionManager); + DataSourceTransactionManager dataSourceTransactionManager = AopTestUtils.getTargetObject(platformTransactionManager); + Assert.assertEquals(applicationContext.getBean(DataSource.class), dataSourceTransactionManager.getDataSource()); + Assert.assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)), platformTransactionManager); + } + + @Test + public void testConfigurationWithDataSourceAndTransactionManager() throws Exception { + ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BatchConfigurationWithDataSourceAndTransactionManager.class); + BatchConfigurer batchConfigurer = applicationContext.getBean(BatchConfigurer.class); + + PlatformTransactionManager platformTransactionManager = batchConfigurer.getTransactionManager(); + Assert.assertSame(transactionManager, platformTransactionManager); + Assert.assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)), transactionManager); + } + + @EnableBatchProcessing + public static class BatchConfigurationWithNoDataSourceAndNoTransactionManager { + @Bean + public BatchConfigurer batchConfigurer() { + return new DefaultBatchConfigurer(); + } + } + + @EnableBatchProcessing + public static class BatchConfigurationWithNoDataSourceAndTransactionManager { + + @Bean + public PlatformTransactionManager transactionManager() { + return transactionManager; + } + + @Bean + public BatchConfigurer batchConfigurer() { + return new DefaultBatchConfigurer() { + @Override + public PlatformTransactionManager getTransactionManager() { + return transactionManager(); + } + }; + } + + } + + @EnableBatchProcessing + public static class BatchConfigurationWithDataSourceAndNoTransactionManager { + @Bean + public DataSource dataSource() { + return createDataSource(); + } + + @Bean + public BatchConfigurer batchConfigurer(DataSource dataSource) { + return new DefaultBatchConfigurer(dataSource); + } + } + + @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 new file mode 100644 index 0000000000..57487998c4 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationWithoutBatchConfigurerTests.java @@ -0,0 +1,148 @@ +/* + * Copyright 2018 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 + * + * http://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.Assert; +import org.junit.Test; + +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.test.util.AopTestUtils; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * @author Mahmoud Ben Hassine + */ +public class TransactionManagerConfigurationWithoutBatchConfigurerTests extends TransactionManagerConfigurationTests { + + @Test + public void testConfigurationWithNoDataSourceAndNoTransactionManager() throws Exception { + ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BatchConfigurationWithNoDataSourceAndNoTransactionManager.class); + Assert.assertTrue(applicationContext.containsBean("transactionManager")); + PlatformTransactionManager platformTransactionManager = applicationContext.getBean(PlatformTransactionManager.class); + Object targetObject = AopTestUtils.getTargetObject(platformTransactionManager); + Assert.assertTrue(targetObject instanceof ResourcelessTransactionManager); + Assert.assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)), targetObject); + } + + @Test + public void testConfigurationWithNoDataSourceAndTransactionManager() throws Exception { + ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BatchConfigurationWithNoDataSourceAndTransactionManager.class); + PlatformTransactionManager platformTransactionManager = applicationContext.getBean(PlatformTransactionManager.class); + Assert.assertSame(transactionManager, platformTransactionManager); + // In this case, the supplied transaction manager won't be used by batch and a ResourcelessTransactionManager will be used instead. + // The user has to provide a custom BatchConfigurer. + Assert.assertTrue(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)) instanceof ResourcelessTransactionManager); + } + + @Test + public void testConfigurationWithDataSourceAndNoTransactionManager() throws Exception { + ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BatchConfigurationWithDataSourceAndNoTransactionManager.class); + Assert.assertTrue(applicationContext.containsBean("transactionManager")); + PlatformTransactionManager platformTransactionManager = applicationContext.getBean(PlatformTransactionManager.class); + Object targetObject = AopTestUtils.getTargetObject(platformTransactionManager); + Assert.assertTrue(targetObject instanceof DataSourceTransactionManager); + DataSourceTransactionManager dataSourceTransactionManager = (DataSourceTransactionManager) targetObject; + Assert.assertEquals(applicationContext.getBean(DataSource.class), dataSourceTransactionManager.getDataSource()); + Assert.assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)), dataSourceTransactionManager); + } + + @Test + public void testConfigurationWithDataSourceAndOneTransactionManager() throws Exception { + ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BatchConfigurationWithDataSourceAndOneTransactionManager.class); + PlatformTransactionManager platformTransactionManager = applicationContext.getBean(PlatformTransactionManager.class); + Assert.assertSame(transactionManager, platformTransactionManager); + // In this case, the supplied transaction manager won't be used by batch and a DataSourceTransactionManager will be used instead. + // The user has to provide a custom BatchConfigurer. + Assert.assertTrue(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)) instanceof DataSourceTransactionManager); + } + + @Test + public void testConfigurationWithDataSourceAndMultipleTransactionManagers() throws Exception { + ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BatchConfigurationWithDataSourceAndMultipleTransactionManagers.class); + PlatformTransactionManager platformTransactionManager = applicationContext.getBean(PlatformTransactionManager.class); + Assert.assertSame(transactionManager2, platformTransactionManager); + // In this case, the supplied primary transaction manager won't be used by batch and a DataSourceTransactionManager will be used instead. + // The user has to provide a custom BatchConfigurer. + Assert.assertTrue(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)) instanceof DataSourceTransactionManager); + + } + + @EnableBatchProcessing + public static class BatchConfigurationWithNoDataSourceAndNoTransactionManager { + + } + + @EnableBatchProcessing + public static class BatchConfigurationWithNoDataSourceAndTransactionManager { + + @Bean + public PlatformTransactionManager transactionManager() { + return transactionManager; + } + } + + @EnableBatchProcessing + public static class BatchConfigurationWithDataSourceAndNoTransactionManager { + + @Bean + public DataSource dataSource() { + return createDataSource(); + } + } + + @EnableBatchProcessing + public static class BatchConfigurationWithDataSourceAndOneTransactionManager { + + @Bean + public DataSource dataSource() { + return createDataSource(); + } + + @Bean + public PlatformTransactionManager transactionManager() { + return transactionManager; + } + } + + @EnableBatchProcessing + public static class BatchConfigurationWithDataSourceAndMultipleTransactionManagers { + + @Bean + public DataSource dataSource() { + return createDataSource(); + } + + @Bean + public PlatformTransactionManager transactionManager() { + return transactionManager; + } + + @Primary + @Bean + public PlatformTransactionManager transactionManager2() { + return transactionManager2; + } + } +}