Allow the transaction manager to be overridden in DefaultBatchConfigurer

Before this commit, it was not possible to override the transaction
manager by subclassing DefaultBatchConfigurer and overriding the
getTransactionManager method.

This commit uses the getTransactionManager method in the initialize method
in order to take into account the transaction manager provided by the user.

Resolves BATCH-2294
This commit is contained in:
Mahmoud Ben Hassine
2018-08-27 23:58:08 +02:00
committed by Michael Minella
parent e83ed1db11
commit a68d70043a
5 changed files with 401 additions and 5 deletions

View File

@@ -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 class DefaultBatchConfigurer implements BatchConfigurer {
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 class DefaultBatchConfigurer implements BatchConfigurer {
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 @@ public class DefaultBatchConfigurer implements BatchConfigurer {
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
factory.setTransactionManager(getTransactionManager());
factory.afterPropertiesSet();
return factory.getObject();
}

View File

@@ -111,6 +111,37 @@ import org.springframework.transaction.PlatformTransactionManager;
* job repository and transaction manager into every step</li>
* </ul>
*
* The transaction manager provided by this annotation will be of type:
*
* <ul>
* <li>{@link org.springframework.batch.support.transaction.ResourcelessTransactionManager}
* if no {@link javax.sql.DataSource} is provided within the context</li>
* <li>{@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
* if a {@link javax.sql.DataSource} is provided within the context</li>
* </ul>
*
* In order to use a custom transaction manager, a custom {@link BatchConfigurer} should be provided. For example:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableBatchProcessing
* public class AppConfig extends DefaultBatchConfigurer {
*
* &#064;Bean
* public Job job() {
* ...
* }
*
* &#064;Override
* public PlatformTransactionManager getTransactionManager() {
* return new MyTransactionManager();
* }
*
* ...
*
* }
* </pre>
*
* If the configuration is specified as <code>modular=true</code> 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

View File

@@ -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();
}
}

View File

@@ -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();
}
};
}
}
}

View File

@@ -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;
}
}
}