Skip to content

Allow the transaction manager to be overridden in DefaultBatchConfigurer #634

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,37 @@
* 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
};
}

}

}
Loading