BATCH-2537: add support for @Primary annotated data sources

Currently, when multiple data sources are defined in the context, an
IllegalStateException is thrown even if one of the data sources is
annotated with @Primary (which should be the one to use).

This commit makes it possible to use the data source annotated with
@Primary when multiple data sources are defined. Note that the context
initialization will still fail (with a UnsatisfiedDependencyException
from Spring's bean factory) if multiple data sources are defined and
none of them is annotated with @Primary. If multiple data sources are
defined and none of them is annotated with @Primary but one of them is
named "dataSource", this data source will be used by the batch
configuration due to autowiring by name (this detail has been documented
in the javadoc of @EnableBatchProcessing).

Resolves BATCH-2537
This commit is contained in:
Mahmoud Ben Hassine
2018-02-16 18:10:26 +01:00
committed by Michael Minella
parent 98add33ab4
commit 7a9a2a9c50
3 changed files with 56 additions and 14 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 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.
@@ -41,6 +41,7 @@ import java.util.Collection;
*
* @author Dave Syer
* @author Michael Minella
* @author Mahmoud Ben Hassine
* @since 2.2
* @see EnableBatchProcessing
*/
@@ -49,7 +50,7 @@ import java.util.Collection;
public abstract class AbstractBatchConfiguration implements ImportAware {
@Autowired(required = false)
private Collection<DataSource> dataSources;
private DataSource dataSource;
private BatchConfigurer configurer;
@@ -93,20 +94,16 @@ public abstract class AbstractBatchConfiguration implements ImportAware {
return this.configurer;
}
if (configurers == null || configurers.isEmpty()) {
if (dataSources == null || dataSources.isEmpty()) {
if (dataSource == null) {
DefaultBatchConfigurer configurer = new DefaultBatchConfigurer();
configurer.initialize();
this.configurer = configurer;
return configurer;
} else if(dataSources != null && dataSources.size() == 1) {
DataSource dataSource = dataSources.iterator().next();
} else {
DefaultBatchConfigurer configurer = new DefaultBatchConfigurer(dataSource);
configurer.initialize();
this.configurer = configurer;
return configurer;
} else {
throw new IllegalStateException("To use the default BatchConfigurer the context must contain no more than " +
"one DataSource, found " + dataSources.size());
}
}
if (configurers.size() > 1) {
@@ -144,4 +141,4 @@ class ScopeConfiguration {
jobScope.setAutoProxy(false);
return jobScope;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 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.
@@ -27,6 +27,7 @@ import org.springframework.batch.core.configuration.support.ApplicationContextFa
import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.PlatformTransactionManager;
@@ -85,7 +86,12 @@ import org.springframework.transaction.PlatformTransactionManager;
* </pre>
*
* If a user does not provide a {@link javax.sql.DataSource} within the context, a Map based
* {@link org.springframework.batch.core.repository.JobRepository} will be used.
* {@link org.springframework.batch.core.repository.JobRepository} will be used. If multiple
* {@link javax.sql.DataSource}s are defined in the context, the one annotated with
* {@link org.springframework.context.annotation.Primary} will be used (Note that if none
* of them is annotated with {@link org.springframework.context.annotation.Primary}, the one
* named <code>dataSource</code> will be used if any, otherwise a {@link UnsatisfiedDependencyException}
* will be thrown).
*
* Note that only one of your configuration classes needs to have the <code>&#064;EnableBatchProcessing</code>
* annotation. Once you have an <code>&#064;EnableBatchProcessing</code> class in your configuration you will have an
@@ -156,6 +162,7 @@ import org.springframework.transaction.PlatformTransactionManager;
* </pre>
*
* @author Dave Syer
* @author Mahmoud Ben Hassine
*
*/
@Target(ElementType.TYPE)

View File

@@ -33,9 +33,11 @@ import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.stereotype.Component;
@@ -57,11 +59,21 @@ public class MapJobRepositoryConfigurationTests {
testConfigurationClass(HsqlBatchConfiguration.class);
}
@Test(expected = IllegalStateException.class)
public void testMultipleDataSources() throws Exception {
@Test(expected = UnsatisfiedDependencyException.class)
public void testMultipleDataSources_whenNoneOfThemIsPrimary() throws Exception {
testConfigurationClass(InvalidBatchConfiguration.class);
}
@Test
public void testMultipleDataSources_whenNoneOfThemIsPrimaryButOneOfThemIsNamed_dataSource_() throws Exception {
testConfigurationClass(ValidBatchConfigurationWithoutPrimaryDataSource.class);
}
@Test
public void testMultipleDataSources_whenOneOfThemIsPrimary() throws Exception {
testConfigurationClass(ValidBatchConfigurationWithPrimaryDataSource.class);
}
private void testConfigurationClass(Class<?> clazz) throws Exception {
GenericApplicationContext context = new AnnotationConfigApplicationContext(clazz);
this.jobLauncher = context.getBean(JobLauncher.class);
@@ -85,11 +97,37 @@ public class MapJobRepositoryConfigurationTests {
}
}
public static class ValidBatchConfigurationWithPrimaryDataSource extends HsqlBatchConfiguration {
@Primary
@Bean
DataSource dataSource2() {
return new PooledEmbeddedDataSource(new EmbeddedDatabaseBuilder().
setName("dataSource2").
addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql").
addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql").
build());
}
}
public static class ValidBatchConfigurationWithoutPrimaryDataSource extends HsqlBatchConfiguration {
@Bean
DataSource dataSource() { // will be autowired by name
return new PooledEmbeddedDataSource(new EmbeddedDatabaseBuilder().
setName("dataSource").
addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql").
addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql").
build());
}
}
public static class HsqlBatchConfiguration extends MapRepositoryBatchConfiguration {
@Bean
DataSource dataSource() {
DataSource dataSource1() {
return new PooledEmbeddedDataSource(new EmbeddedDatabaseBuilder().
setName("dataSource1").
addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql").
addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql").
build());