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: + * + *
+ * @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;
+ }
+ }
+}