Revisit the configuration code of EnableBatchProcessing

Before this commit, the configuration of infrastructure beans
was confusing and not straightforward to customize. This commit
changes the way Batch infrastructure beans are configured.
The most important changes are:

* EnableBatchProcessing now provides new attributes to
configure properties of infrastructure beans
* Bean registration is now done programmatically with a
BeanDefinitionRegistrar instead of importing a class with
statically annotated bean definition methods
* Bean are now resolved from the application context directly
instead of being resolved from a BatchConfigurer
* Both a data source and a transaction manager are now
required to be defined in the application context
* A new configuration class called DefaultBatchConfiguration
with default infrastructure bean definitions is now provided
and can be extended to customize the default configuration

Resolves #3942
This commit is contained in:
Mahmoud Ben Hassine
2022-09-14 10:39:21 +02:00
parent f39f07075a
commit 48e437ae17
25 changed files with 1131 additions and 969 deletions

View File

@@ -36,4 +36,21 @@ public class BatchConfigurationException extends RuntimeException {
super(t);
}
/**
* Create an exception with the given message.
* @param message the error message
*/
public BatchConfigurationException(String message) {
super(message);
}
/**
* Create an exception with the given message and {@link Throwable}.
* @param message the error message
* @param cause an exception to be wrapped
*/
public BatchConfigurationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -1,145 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://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 java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.configuration.support.MapJobRegistry;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.PlatformTransactionManager;
/**
* Base {@code Configuration} class that provides a common structure for enabling and
* using Spring Batch. Customization is available by implementing the
* {@link BatchConfigurer} interface.
*
* @author Dave Syer
* @author Michael Minella
* @author Mahmoud Ben Hassine
* @since 2.2
* @see EnableBatchProcessing
*/
@Configuration(proxyBeanMethods = false)
@Import(ScopeConfiguration.class)
public abstract class AbstractBatchConfiguration {
private static final Log logger = LogFactory.getLog(AbstractBatchConfiguration.class);
@Autowired
protected ApplicationContext context;
private JobRegistry jobRegistry = new MapJobRegistry();
/**
* Establish the {@link JobRepository} for the batch execution.
* @return The instance of the {@link JobRepository}.
* @throws Exception The {@link Exception} thrown if an error occurs.
*/
@Bean
public abstract JobRepository jobRepository() throws Exception;
/**
* Establish the {@link JobLauncher} for the batch execution.
* @return The instance of the {@link JobLauncher}.
* @throws Exception The {@link Exception} thrown if an error occurs.
*/
@Bean
public abstract JobLauncher jobLauncher() throws Exception;
/**
* Establish the {@link JobExplorer} for the batch execution.
* @return The instance of the {@link JobExplorer}.
* @throws Exception The {@link Exception} thrown if an error occurs.
*/
@Bean
public abstract JobExplorer jobExplorer() throws Exception;
/**
* Establish the {@link JobRegistry} for the batch execution.
* @return The instance of the {@link JobRegistry}.
* @throws Exception The {@link Exception} thrown if an error occurs.
*/
@Bean
public JobRegistry jobRegistry() throws Exception {
return this.jobRegistry;
}
/**
* If a {@link BatchConfigurer} exists, return it. Otherwise, create a
* {@link DefaultBatchConfigurer}. If more than one configurer is present, an
* {@link IllegalStateException} is thrown.
* @return The {@link BatchConfigurer} that was in the configurers collection or the
* default one created.
*/
protected BatchConfigurer getOrCreateConfigurer() {
BatchConfigurer batchConfigurer = getConfigurer();
if (batchConfigurer == null) {
batchConfigurer = createDefaultConfigurer();
}
return batchConfigurer;
}
private BatchConfigurer getConfigurer() {
Map<String, BatchConfigurer> configurers = this.context.getBeansOfType(BatchConfigurer.class);
if (configurers != null && configurers.size() > 1) {
throw new IllegalStateException(
"To use a custom BatchConfigurer the context must contain precisely one, found "
+ configurers.size());
}
if (configurers != null && configurers.size() == 1) {
return configurers.entrySet().iterator().next().getValue();
}
return null;
}
private BatchConfigurer createDefaultConfigurer() {
DataSource dataSource = getDataSource();
DefaultBatchConfigurer configurer = new DefaultBatchConfigurer(dataSource);
configurer.initialize();
return configurer;
}
private DataSource getDataSource() {
Map<String, DataSource> dataSources = this.context.getBeansOfType(DataSource.class);
if (dataSources == null || (dataSources != null && dataSources.isEmpty())) {
throw new IllegalStateException("To use the default BatchConfigurer, the application context must"
+ " contain at least one data source but none was found.");
}
if (dataSources != null && dataSources.size() > 1) {
logger.info("Multiple data sources are defined in the application context. The data source to"
+ " use in the default BatchConfigurer will be the one selected by Spring according"
+ " to the rules of getting the primary bean from the application context.");
return this.context.getBean(DataSource.class);
}
return dataSources.entrySet().iterator().next().getValue();
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2022 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
*
* https://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 java.util.Iterator;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.configuration.support.ApplicationContextFactory;
import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar;
import org.springframework.batch.core.configuration.support.DefaultJobLoader;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
/**
* Post processor that configures the {@link AutomaticJobRegistrar} registered by
* {@link BatchRegistrar} with required properties.
*
* @author Mahmoud Ben Hassine
* @since 5.0
*/
class AutomaticJobRegistrarBeanPostProcessor implements BeanFactoryPostProcessor, BeanPostProcessor {
private ConfigurableListableBeanFactory beanFactory;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AutomaticJobRegistrar) {
AutomaticJobRegistrar automaticJobRegistrar = (AutomaticJobRegistrar) bean;
automaticJobRegistrar.setJobLoader(new DefaultJobLoader(this.beanFactory.getBean(JobRegistry.class)));
for (ApplicationContextFactory factory : this.beanFactory.getBeansOfType(ApplicationContextFactory.class)
.values()) {
automaticJobRegistrar.addApplicationContextFactory(factory);
}
return automaticJobRegistrar;
}
return bean;
}
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://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 org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
/**
* Base {@code Configuration} class that provides common structure for enabling and using
* Spring Batch. Customization is available by implementing the {@link BatchConfigurer}
* interface.
*
* @author Dave Syer
* @since 2.2
* @see EnableBatchProcessing
*/
public class BatchConfigurationSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<?> annotationType = EnableBatchProcessing.class;
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(annotationType.getName(), false));
Assert.notNull(attributes, String.format("@%s is not present on importing class '%s' as expected",
annotationType.getSimpleName(), importingClassMetadata.getClassName()));
String[] imports;
if (attributes.containsKey("modular") && attributes.getBoolean("modular")) {
imports = new String[] { ModularBatchConfiguration.class.getName() };
}
else {
imports = new String[] { SimpleBatchConfiguration.class.getName() };
}
return imports;
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://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 org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.transaction.PlatformTransactionManager;
/**
* Strategy interface that users can provide as a factory for custom components needed by
* a Batch system.
*
* @author Dave Syer
* @author Mahmoud Ben Hassine
*
*/
public interface BatchConfigurer {
/**
* @return The {@link JobRepository}.
* @throws Exception The {@link Exception} thrown if an error occurs.
*/
JobRepository getJobRepository() throws Exception;
/**
* @return The {@link JobLauncher}.
* @throws Exception The {@link Exception} thrown if an error occurs.
*/
JobLauncher getJobLauncher() throws Exception;
/**
* @return The {@link JobExplorer}.
* @throws Exception The {@link Exception} thrown if an error occurs.
*/
JobExplorer getJobExplorer() throws Exception;
}

View File

@@ -0,0 +1,234 @@
/*
* Copyright 2022 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
*
* https://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 java.nio.charset.Charset;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar;
import org.springframework.batch.core.configuration.support.DefaultJobLoader;
import org.springframework.batch.core.configuration.support.MapJobRegistry;
import org.springframework.batch.core.explore.support.JobExplorerFactoryBean;
import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.log.LogMessage;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StopWatch;
/**
* Base registrar that provides common infrastrucutre beans for enabling and using Spring
* Batch in a declarative way through {@link EnableBatchProcessing}.
*
* @author Mahmoud Ben Hassine
* @since 5.0
* @see EnableBatchProcessing
*/
class BatchRegistrar implements ImportBeanDefinitionRegistrar {
private static final Log LOGGER = LogFactory.getLog(BatchRegistrar.class);
private static final String MISSING_BEAN_ERROR_MESSAGE = "Unable to find bean '%s' for attribute %s of annotation %s on class %s";
private static final String MISSING_ANNOTATION_ERROR_MESSAGE = "EnableBatchProcessing is not present on importing class '%s' as expected";
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
StopWatch watch = new StopWatch();
watch.start();
validateState(importingClassMetadata);
EnableBatchProcessing batchAnnotation = importingClassMetadata.getAnnotations().get(EnableBatchProcessing.class)
.synthesize();
String importingClassName = importingClassMetadata.getClassName();
registerJobRepository(registry, batchAnnotation, importingClassName);
registerJobExplorer(registry, batchAnnotation, importingClassName);
registerJobLauncher(registry, batchAnnotation, importingClassName);
registerJobRegistry(registry);
registerAutomaticJobRegistrar(registry, batchAnnotation);
watch.stop();
LOGGER.info(LogMessage.format("Finished Spring Batch infrastrucutre beans configuration in %s ms.",
watch.getLastTaskTimeMillis()));
}
private void validateState(AnnotationMetadata importingClassMetadata) {
if (!importingClassMetadata.isAnnotated(EnableBatchProcessing.class.getName())) {
String className = importingClassMetadata.getClassName();
String errorMessage = String.format(MISSING_ANNOTATION_ERROR_MESSAGE, className);
throw new IllegalStateException(errorMessage);
}
}
private void registerJobRepository(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation,
String importingClassName) {
if (registry.containsBeanDefinition("jobRepository")) {
LOGGER.info("Bean jobRepository already defined in the application context, skipping"
+ " the registration of a jobRepository");
return;
}
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(JobRepositoryFactoryBean.class);
// set mandatory properties
String dataSourceRef = batchAnnotation.dataSourceRef();
if (!registry.containsBeanDefinition(dataSourceRef)) {
String errorMessage = String.format(MISSING_BEAN_ERROR_MESSAGE, dataSourceRef, "dataSourceRef",
batchAnnotation, importingClassName);
throw new IllegalStateException(errorMessage);
}
else {
beanDefinitionBuilder.addPropertyReference("dataSource", dataSourceRef);
}
String transactionManagerRef = batchAnnotation.transactionManagerRef();
if (!registry.containsBeanDefinition(transactionManagerRef)) {
String errorMessage = String.format(MISSING_BEAN_ERROR_MESSAGE, transactionManagerRef,
"transactionManagerRef", batchAnnotation, importingClassName);
throw new IllegalStateException(errorMessage);
}
else {
beanDefinitionBuilder.addPropertyReference("transactionManager", transactionManagerRef);
}
// set optional properties
String executionContextSerializerRef = batchAnnotation.executionContextSerializerRef();
if (registry.containsBeanDefinition(executionContextSerializerRef)) {
beanDefinitionBuilder.addPropertyReference("serializer", executionContextSerializerRef);
}
String lobHandlerRef = batchAnnotation.lobHandlerRef();
if (registry.containsBeanDefinition(lobHandlerRef)) {
beanDefinitionBuilder.addPropertyReference("lobHandler", lobHandlerRef);
}
String incrementerFactoryRef = batchAnnotation.incrementerFactoryRef();
if (registry.containsBeanDefinition(incrementerFactoryRef)) {
beanDefinitionBuilder.addPropertyReference("incrementerFactory", incrementerFactoryRef);
}
String charset = batchAnnotation.charset();
if (charset != null) {
beanDefinitionBuilder.addPropertyValue("charset", Charset.forName(charset));
}
String tablePrefix = batchAnnotation.tablePrefix();
if (tablePrefix != null) {
beanDefinitionBuilder.addPropertyValue("tablePrefix", tablePrefix);
}
beanDefinitionBuilder.addPropertyValue("maxVarCharLength", batchAnnotation.maxVarCharLength());
beanDefinitionBuilder.addPropertyValue("clobType", batchAnnotation.clobType());
registry.registerBeanDefinition("jobRepository", beanDefinitionBuilder.getBeanDefinition());
}
private void registerJobExplorer(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation,
String importingClassName) {
if (registry.containsBeanDefinition("jobExplorer")) {
LOGGER.info("Bean jobExplorer already defined in the application context, skipping"
+ " the registration of a jobExplorer");
return;
}
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(JobExplorerFactoryBean.class);
// set mandatory properties
String dataSourceRef = batchAnnotation.dataSourceRef();
if (!registry.containsBeanDefinition(dataSourceRef)) {
String errorMessage = String.format(MISSING_BEAN_ERROR_MESSAGE, dataSourceRef, "dataSourceRef",
batchAnnotation, importingClassName);
throw new IllegalStateException(errorMessage);
}
else {
beanDefinitionBuilder.addPropertyReference("dataSource", dataSourceRef);
}
// set optional properties
String executionContextSerializerRef = batchAnnotation.executionContextSerializerRef();
if (registry.containsBeanDefinition(executionContextSerializerRef)) {
beanDefinitionBuilder.addPropertyReference("serializer", executionContextSerializerRef);
}
String lobHandlerRef = batchAnnotation.lobHandlerRef();
if (registry.containsBeanDefinition(lobHandlerRef)) {
beanDefinitionBuilder.addPropertyReference("lobHandler", lobHandlerRef);
}
String charset = batchAnnotation.charset();
if (charset != null) {
beanDefinitionBuilder.addPropertyValue("charset", Charset.forName(charset));
}
String tablePrefix = batchAnnotation.tablePrefix();
if (tablePrefix != null) {
beanDefinitionBuilder.addPropertyValue("tablePrefix", tablePrefix);
}
registry.registerBeanDefinition("jobExplorer", beanDefinitionBuilder.getBeanDefinition());
}
private void registerJobLauncher(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation,
String importingClassName) {
if (registry.containsBeanDefinition("jobLauncher")) {
LOGGER.info("Bean jobLauncher already defined in the application context, skipping"
+ " the registration of a jobLauncher");
return;
}
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(TaskExecutorJobLauncher.class);
// set mandatory properties
beanDefinitionBuilder.addPropertyReference("jobRepository", "jobRepository");
// set optional properties
String taskExecutorRef = batchAnnotation.taskExecutorRef();
if (registry.containsBeanDefinition(taskExecutorRef)) {
beanDefinitionBuilder.addPropertyReference("taskExecutor", taskExecutorRef);
}
registry.registerBeanDefinition("jobLauncher", beanDefinitionBuilder.getBeanDefinition());
}
private void registerJobRegistry(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition("jobRegistry")) {
LOGGER.info("Bean jobRegistry already defined in the application context, skipping"
+ " the registration of a jobRegistry");
return;
}
BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(MapJobRegistry.class)
.getBeanDefinition();
registry.registerBeanDefinition("jobRegistry", beanDefinition);
}
private void registerAutomaticJobRegistrar(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) {
if (!batchAnnotation.modular()) {
return;
}
if (registry.containsBeanDefinition("jobRegistrar")) {
LOGGER.info("Bean jobRegistrar already defined in the application context, skipping"
+ " the registration of a jobRegistrar");
return;
}
BeanDefinition jobLoaderBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(DefaultJobLoader.class)
.addPropertyReference("jobRegistry", "jobRegistry").getBeanDefinition();
registry.registerBeanDefinition("jobLoader", jobLoaderBeanDefinition);
BeanDefinition jobRegistrarBeanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(AutomaticJobRegistrar.class).addPropertyReference("jobLoader", "jobLoader")
.getBeanDefinition();
registry.registerBeanDefinition("jobRegistrar", jobRegistrarBeanDefinition);
}
}

View File

@@ -1,175 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://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 jakarta.annotation.PostConstruct;
import javax.sql.DataSource;
import org.springframework.batch.core.configuration.BatchConfigurationException;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.explore.support.JobExplorerFactoryBean;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
/**
* Default implementation of the {@link BatchConfigurer}.
*/
@Component
public class DefaultBatchConfigurer implements BatchConfigurer, InitializingBean {
private DataSource dataSource;
private PlatformTransactionManager transactionManager;
private JobRepository jobRepository;
private JobLauncher jobLauncher;
private JobExplorer jobExplorer;
/**
* Create a new {@link DefaultBatchConfigurer} with the passed datasource. This
* constructor configures a default {@link JdbcTransactionManager}.
* @param dataSource to use for the job repository and job explorer
*/
public DefaultBatchConfigurer(DataSource dataSource) {
this(dataSource, new JdbcTransactionManager(dataSource));
}
/**
* Create a new {@link DefaultBatchConfigurer} with the given datasource and
* transaction manager.
* @param dataSource The data source to use for the job repository and job explorer.
* @param transactionManager The transaction manager to use for the job repository.
*/
public DefaultBatchConfigurer(DataSource dataSource, PlatformTransactionManager transactionManager) {
Assert.notNull(dataSource, "DataSource must not be null");
Assert.notNull(transactionManager, "transactionManager must not be null");
this.dataSource = dataSource;
this.transactionManager = transactionManager;
initialize();
}
/**
* Sets the dataSource.
* @param dataSource The data source to use. Must not be {@code null}.
*/
public void setDataSource(DataSource dataSource) {
Assert.notNull(dataSource, "DataSource must not be null");
this.dataSource = dataSource;
}
/**
* @return The {@link DataSource} used by the {@link DefaultBatchConfigurer}.
*/
public DataSource getDataSource() {
return this.dataSource;
}
@Override
public JobRepository getJobRepository() {
return this.jobRepository;
}
@Override
public JobLauncher getJobLauncher() {
return this.jobLauncher;
}
@Override
public JobExplorer getJobExplorer() {
return this.jobExplorer;
}
public PlatformTransactionManager getTransactionManager() {
return this.transactionManager;
}
/**
* Set the transaction manager.
* @param transactionManager the transaction manager to use. Must not be {@code null}.
*/
public void setTransactionManager(PlatformTransactionManager transactionManager) {
Assert.notNull(transactionManager, "TransactionManager must not be null");
this.transactionManager = transactionManager;
}
@Override
public void afterPropertiesSet() throws Exception {
initialize();
}
/**
* Initialize the {@link DefaultBatchConfigurer} with the {@link JobRepository},
* {@link JobExplorer}, and {@link JobLauncher}.
*/
@PostConstruct
public void initialize() {
try {
this.jobRepository = createJobRepository();
this.jobExplorer = createJobExplorer();
this.jobLauncher = createJobLauncher();
}
catch (Exception e) {
throw new BatchConfigurationException(e);
}
}
/**
* @return An instance of {@link JobLauncher}.
* @throws Exception The {@link Exception} that is thrown if an error occurs while
* creating the {@link JobLauncher}.
*/
protected JobLauncher createJobLauncher() throws Exception {
TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher();
jobLauncher.setJobRepository(this.jobRepository);
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
/**
* @return An instance of {@link JobRepository}.
* @throws Exception The {@link Exception} that is thrown if an error occurs while
* creating the {@link JobRepository}.
*/
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(getDataSource());
factory.setTransactionManager(getTransactionManager());
factory.afterPropertiesSet();
return factory.getObject();
}
/**
* @return An instance of {@link JobExplorer}.
* @throws Exception The {@link Exception} that is thrown if an error occurs while
* creating the {@link JobExplorer}.
*/
protected JobExplorer createJobExplorer() throws Exception {
JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
jobExplorerFactoryBean.setDataSource(getDataSource());
jobExplorerFactoryBean.afterPropertiesSet();
return jobExplorerFactoryBean.getObject();
}
}

View File

@@ -20,14 +20,18 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.configuration.support.ApplicationContextFactory;
import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar;
import org.springframework.batch.core.configuration.support.ScopeConfiguration;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao;
import org.springframework.context.annotation.Import;
/**
@@ -63,31 +67,10 @@ import org.springframework.context.annotation.Import;
* }
* </pre>
*
* You should provide a {@link DataSource} as a bean in the context or else implement
* {@link BatchConfigurer} in the configuration class itself -- for example:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableBatchProcessing
* public class AppConfig extends DefaultBatchConfigurer {
*
* &#064;Bean
* public Job job() {
* ...
* }
*
* &#064;Override
* protected JobRepository createJobRepository() {
* ...
* }
*
* ...
*
* }
* </pre>
*
* If multiple {@link javax.sql.DataSource} instances are defined in the context, the
* primary autowire candidate is used. Otherwise, an exception is thrown.
* This annotation configures JDBC-based Batch infrastrcuture beans, so you must provide a
* {@link DataSource} and a
* {@link org.springframework.transaction.PlatformTransactionManager} as a beans in the
* application context.
*
* Note that only one of your configuration classes needs to have the
* <code>&#064;EnableBatchProcessing</code> annotation. Once you have an
@@ -110,33 +93,6 @@ import org.springframework.context.annotation.Import;
* {@link org.springframework.batch.core.explore.support.SimpleJobExplorer})</li>
* </ul>
*
* The transaction manager provided by this annotation is of type
* {@link org.springframework.jdbc.support.JdbcTransactionManager} and is configured with
* the {@link javax.sql.DataSource} provided within the context.
*
* To use a custom transaction manager, you should provide a custom
* {@link BatchConfigurer} -- 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>, the context also
* contains an {@link AutomaticJobRegistrar}. The job registrar is useful for modularizing
* your configuration if there are multiple jobs. It works by creating separate child
@@ -166,8 +122,8 @@ import org.springframework.context.annotation.Import;
* </pre>
*
* Note that a modular parent context, in general, should <em>not</em> itself contain
* &#64;Bean definitions for job, especially if a {@link BatchConfigurer} is provided,
* because cyclic configuration dependencies are likely to develop.
* &#64;Bean definitions for job, because cyclic configuration dependencies are likely to
* develop.
*
* <p>
* For reference, compare the first example shown earlier to the following Spring XML
@@ -181,6 +137,7 @@ import org.springframework.context.annotation.Import;
* <step id="step1" .../>
* <step id="step2" .../>
* </job>
* <beans:bean id="dataSource" .../>
* <beans:bean id="transactionManager" .../>
* <beans:bean id="jobLauncher" class=
"org.springframework.batch.core.launch.support.TaskExecutorJobLauncher">
@@ -197,7 +154,7 @@ import org.springframework.context.annotation.Import;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(BatchConfigurationSelector.class)
@Import({ BatchRegistrar.class, ScopeConfiguration.class, AutomaticJobRegistrarBeanPostProcessor.class })
public @interface EnableBatchProcessing {
/**
@@ -210,4 +167,68 @@ public @interface EnableBatchProcessing {
*/
boolean modular() default false;
/**
* Set the data source to use in the job repository and job explorer.
* @return the bean name of the data source to use. Default to {@literal dataSource}.
*/
String dataSourceRef() default "dataSource";
/**
* Set the transaction manager to use in the job repository.
* @return the bean name of the transaction manager to use. Defaults to
* {@literal transactionManager}
*/
String transactionManagerRef() default "transactionManager";
/**
* Set the execution context serializer to use in the job repository and job explorer.
* @return the bean name of the execution context serializer to use. Default to
* {@literal executionContextSerializer}.
*/
String executionContextSerializerRef() default "executionContextSerializer";
/**
* The charset to use in the job repository and job explorer
* @return the charset to use. Defaults to {@literal UTF-8}.
*/
String charset() default "UTF-8";
/**
* The Batch tables prefix. Defaults to {@literal "BATCH_"}.
* @return the Batch table prefix
*/
String tablePrefix() default AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX;
/**
* The maximum lenght of exit messages in the database.
* @return the maximum lenght of exit messages in the database
*/
int maxVarCharLength() default AbstractJdbcBatchMetadataDao.DEFAULT_EXIT_MESSAGE_LENGTH;
/**
* The incrementer factory to use in various DAOs.
* @return the bean name of the incrementer factory to use. Defaults to
* {@literal incrementerFactory}.
*/
String incrementerFactoryRef() default "incrementerFactory";
/**
* The large object handler to use in job repository and job explorer.
* @return the bean name of the lob handler to use. Defaults to {@literal lobHandler}.
*/
String lobHandlerRef() default "lobHandler";
/**
* The type of large objects.
* @return the type of large objects.
*/
int clobType() default Types.CLOB;
/**
* Set the task executor to use in the job launcher.
* @return the bean name of the task executor to use. Defaults to
* {@literal taskExecutor}
*/
String taskExecutorRef() default "taskExecutor";
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://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 java.util.Collection;
import org.springframework.batch.core.configuration.support.ApplicationContextFactory;
import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar;
import org.springframework.batch.core.configuration.support.DefaultJobLoader;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
/**
* Base {@code Configuration} class providing common structure for enabling and using
* Spring Batch. Customization is available by implementing the {@link BatchConfigurer}
* interface.
*
* @author Dave Syer
* @author Mahmoud Ben Hassine
* @since 2.2
* @see EnableBatchProcessing
*/
@Configuration(proxyBeanMethods = false)
public class ModularBatchConfiguration extends SimpleBatchConfiguration {
private AutomaticJobRegistrar registrar = new AutomaticJobRegistrar();
/**
* Creates a {@link AutomaticJobRegistrar} bean.
* @return a new instance of {@link AutomaticJobRegistrar}.
* @throws Exception if an error occurs.
*/
@Bean
public AutomaticJobRegistrar jobRegistrar() throws Exception {
registrar.setJobLoader(new DefaultJobLoader(jobRegistry()));
for (ApplicationContextFactory factory : context.getBeansOfType(ApplicationContextFactory.class).values()) {
registrar.addApplicationContextFactory(factory);
}
return registrar;
}
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://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 java.util.Collection;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
/**
* Base {@code Configuration} class to provide a common structure for enabling and using
* Spring Batch. Customization is available by implementing the {@link BatchConfigurer}
* interface.
*
* @author Dave Syer
* @author Mahmoud Ben Hassine
* @since 2.2
* @see EnableBatchProcessing
*/
@Configuration(proxyBeanMethods = false)
public class SimpleBatchConfiguration extends AbstractBatchConfiguration {
@Override
public JobRepository jobRepository() throws Exception {
return getOrCreateConfigurer().getJobRepository();
}
@Override
public JobLauncher jobLauncher() throws Exception {
return getOrCreateConfigurer().getJobLauncher();
}
@Override
public JobExplorer jobExplorer() throws Exception {
return getOrCreateConfigurer().getJobExplorer();
}
}

View File

@@ -0,0 +1,345 @@
/*
* Copyright 2012-2022 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
*
* https://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.support;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.sql.Types;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.configuration.BatchConfigurationException;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.explore.support.JobExplorerFactoryBean;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher;
import org.springframework.batch.core.repository.ExecutionContextSerializer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao;
import org.springframework.batch.core.repository.dao.Jackson2ExecutionContextStringSerializer;
import org.springframework.batch.core.repository.dao.JdbcExecutionContextDao;
import org.springframework.batch.core.repository.dao.JdbcJobExecutionDao;
import org.springframework.batch.core.repository.dao.JdbcStepExecutionDao;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.batch.item.database.support.DataFieldMaxValueIncrementerFactory;
import org.springframework.batch.item.database.support.DefaultDataFieldMaxValueIncrementerFactory;
import org.springframework.batch.support.DatabaseType;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Isolation;
/**
* Base {@link Configuration} class that provides common JDBC-based infrastructure beans
* for enabling and using Spring Batch.
*
* This configuration class configures and registers the following beans in the
* application context:
*
* <ul>
* <li>a {@link JobRepository} named "jobRepository"</li>
* <li>a {@link JobExplorer} named "jobExplorer"</li>
* <li>a {@link JobLauncher} named "jobLauncher"</li>
* <li>a {@link JobRegistry} named "jobRegistry"</li>
* <li>a {@link org.springframework.batch.core.scope.StepScope} named "stepScope"</li>
* <li>a {@link org.springframework.batch.core.scope.JobScope} named "jobScope"</li>
* </ul>
*
* Customization is possible by extending the class and overriding getters.
*
* A typical usage of this class is as follows:
*
* <pre class="code">
* &#064;Configuration
* public class MyJobConfiguration extends AbstractBatchConfiguration {
*
* &#064;Bean
* public Job job(JobRepository jobRepository) {
* return new JobBuilder("myJob", jobRepository)
* // define job flow as needed
* .build();
* }
*
* }
* </pre>
*
* @author Dave Syer
* @author Michael Minella
* @author Mahmoud Ben Hassine
* @since 2.2
*/
@Configuration(proxyBeanMethods = false)
@Import(ScopeConfiguration.class)
public class DefaultBatchConfiguration implements ApplicationContextAware {
private static final Log LOGGER = LogFactory.getLog(DefaultBatchConfiguration.class);
@Autowired
protected ApplicationContext applicationContext;
private final JobRegistry jobRegistry = new MapJobRegistry();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Bean
public JobRepository jobRepository() throws BatchConfigurationException {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
try {
jobRepositoryFactoryBean.setDataSource(getDataSource());
jobRepositoryFactoryBean.setTransactionManager(getTransactionManager());
jobRepositoryFactoryBean.setDatabaseType(getDatabaseType());
jobRepositoryFactoryBean.setIncrementerFactory(getIncrementerFactory());
jobRepositoryFactoryBean.setClobType(getClobType());
jobRepositoryFactoryBean.setTablePrefix(getTablePrefix());
jobRepositoryFactoryBean.setSerializer(getExecutionContextSerializer());
jobRepositoryFactoryBean.setJdbcOperations(getJdbcOperations());
jobRepositoryFactoryBean.setLobHandler(getLobHandler());
jobRepositoryFactoryBean.setCharset(getCharset());
jobRepositoryFactoryBean.setMaxVarCharLength(getMaxVarCharLength());
jobRepositoryFactoryBean.setIsolationLevelForCreate(getIsolationLevelForCreate());
jobRepositoryFactoryBean.setValidateTransactionState(getValidateTransactionState());
jobRepositoryFactoryBean.afterPropertiesSet();
return jobRepositoryFactoryBean.getObject();
}
catch (Exception e) {
throw new BatchConfigurationException("Unable to configure the default job repository", e);
}
}
@Bean
public JobLauncher jobLauncher() throws BatchConfigurationException {
TaskExecutorJobLauncher taskExecutorJobLauncher = new TaskExecutorJobLauncher();
taskExecutorJobLauncher.setJobRepository(jobRepository());
taskExecutorJobLauncher.setTaskExecutor(getTaskExector());
try {
taskExecutorJobLauncher.afterPropertiesSet();
return taskExecutorJobLauncher;
}
catch (Exception e) {
throw new BatchConfigurationException("Unable to configure the default job launcher", e);
}
}
@Bean
public JobExplorer jobExplorer() throws BatchConfigurationException {
JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
jobExplorerFactoryBean.setDataSource(getDataSource());
jobExplorerFactoryBean.setJdbcOperations(getJdbcOperations());
jobExplorerFactoryBean.setCharset(getCharset());
jobExplorerFactoryBean.setTablePrefix(getTablePrefix());
jobExplorerFactoryBean.setLobHandler(getLobHandler());
jobExplorerFactoryBean.setSerializer(getExecutionContextSerializer());
try {
jobExplorerFactoryBean.afterPropertiesSet();
return jobExplorerFactoryBean.getObject();
}
catch (Exception e) {
throw new BatchConfigurationException("Unable to configure the default job explorer", e);
}
}
@Bean
public JobRegistry jobRegistry() throws Exception {
return this.jobRegistry; // FIXME returning a new instance here does not work
}
/*
* Getters to customize the configuration of infrastructure beans
*/
/**
* Return the data source to use for Batch meta-data. Defaults to the bean of type
* {@link DataSource} and named "dataSource" in the application context.
* @return The data source to use for Batch meta-data
*/
protected DataSource getDataSource() {
String errorMessage = " To use the default configuration, a data source bean named 'dataSource'"
+ " should be defined in the application context but none was found. Override getDataSource()"
+ " to provide the data source to use for Batch meta-data.";
if (this.applicationContext.getBeansOfType(DataSource.class).isEmpty()) {
throw new BatchConfigurationException(
"Unable to find a DataSource bean in the applicaion context." + errorMessage);
}
else {
if (!this.applicationContext.containsBean("dataSource")) {
throw new BatchConfigurationException(errorMessage);
}
}
return this.applicationContext.getBean("dataSource", DataSource.class);
}
/**
* Return the transaction manager to use for the job repository. Defaults to the bean
* of type {@link PlatformTransactionManager} and named "transactionManager" in the
* application context.
* @return The transaction manager to use for the job repository
*/
protected PlatformTransactionManager getTransactionManager() {
String errorMessage = " To use the default configuration, a transaction manager bean named 'transactionManager'"
+ " should be defined in the application context but none was found. Override getTransactionManager()"
+ " to provide the transaction manager to use for the job repository.";
if (this.applicationContext.getBeansOfType(PlatformTransactionManager.class).isEmpty()) {
throw new BatchConfigurationException(
"Unable to find a PlatformTransactionManager bean in the applicaion context." + errorMessage);
}
else {
if (!this.applicationContext.containsBean("transactionManager")) {
throw new BatchConfigurationException(errorMessage);
}
}
return this.applicationContext.getBean("transactionManager", PlatformTransactionManager.class);
}
/**
* Return the value of the {@code validateTransactionState} parameter. Defaults to
* {@code true}.
* @return true if the transaction state should be validated, false otherwise
*/
protected boolean getValidateTransactionState() {
return true;
}
/**
* Return the transaction isolation level when creating job executions. Defaults to
* {@link Isolation#SERIALIZABLE}.
* @return the transaction isolation level when creating job executions
*/
protected Isolation getIsolationLevelForCreate() {
return Isolation.SERIALIZABLE;
}
/**
* Return the length of long string columns in database. Do not override this if you
* haven't modified the schema. Note this value will be used for the exit message in
* both {@link JdbcJobExecutionDao} and {@link JdbcStepExecutionDao} and also the
* short version of the execution context in {@link JdbcExecutionContextDao} . For
* databases with multi-byte character sets this number can be smaller (by up to a
* factor of 2 for 2-byte characters) than the declaration of the column length in the
* DDL for the tables. Defaults to
* {@link AbstractJdbcBatchMetadataDao#DEFAULT_EXIT_MESSAGE_LENGTH}
*/
protected int getMaxVarCharLength() {
return AbstractJdbcBatchMetadataDao.DEFAULT_EXIT_MESSAGE_LENGTH;
}
/**
* Return the prefix of Batch meta-data tables. Defaults to
* {@link AbstractJdbcBatchMetadataDao#DEFAULT_TABLE_PREFIX}.
* @return the prefix of meta-data tables
*/
protected String getTablePrefix() {
return AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX;
}
/**
* Return the {@link Charset} to use when serializing/deserializing the execution
* context. Defaults to "UTF-8".
* @return the charset to use when serializing/deserializing the execution context
*/
protected Charset getCharset() {
return StandardCharsets.UTF_8;
}
/**
* A special handler for large objects. The default is usually fine, except for some
* (usually older) versions of Oracle.
* @return the {@link LobHandler} to use
*
*/
protected LobHandler getLobHandler() {
return new DefaultLobHandler();
}
/**
* Return the {@link JdbcOperations}. If this property is not overridden, a new
* {@link JdbcTemplate} will be created for the configured data source by default.
* @return the {@link JdbcOperations} to use
*/
protected JdbcOperations getJdbcOperations() {
return new JdbcTemplate(getDataSource());
}
/**
* A custom implementation of the {@link ExecutionContextSerializer}. The default, if
* not injected, is the {@link Jackson2ExecutionContextStringSerializer}.
* @return the serializer to use to serialize/deserialize the execution context
*/
protected ExecutionContextSerializer getExecutionContextSerializer() {
return new Jackson2ExecutionContextStringSerializer();
}
/**
* Return the value from {@link java.sql.Types} class to indicate the type to use for
* a CLOB
* @return the value from {@link java.sql.Types} class to indicate the type to use for
* a CLOB
*/
protected int getClobType() {
return Types.CLOB;
}
/**
* Return the factory for creating {@link DataFieldMaxValueIncrementer}
* implementations used to increment entity IDs in meta-data tables.
* @return the factory for creating {@link DataFieldMaxValueIncrementer}
* implementations.
*/
protected DataFieldMaxValueIncrementerFactory getIncrementerFactory() {
return new DefaultDataFieldMaxValueIncrementerFactory(getDataSource());
}
/**
* Return the database type. The default will be introspected from the JDBC meta-data
* of the data source.
* @return the database type
* @throws MetaDataAccessException if an error occurs when trying to get the database
* type of JDBC meta-data
*
*/
protected String getDatabaseType() throws MetaDataAccessException {
return DatabaseType.fromMetaData(getDataSource()).name();
}
/**
* Return the {@link TaskExecutor} to use in the the job launcher. Defaults to
* {@link SyncTaskExecutor}.
* @return the {@link TaskExecutor} to use in the the job launcher.
*/
protected TaskExecutor getTaskExector() {
return new SyncTaskExecutor();
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.configuration.annotation;
package org.springframework.batch.core.configuration.support;
import org.springframework.batch.core.scope.JobScope;
import org.springframework.batch.core.scope.StepScope;
@@ -24,6 +24,7 @@ import org.springframework.context.annotation.Configuration;
* {@code Configuration} class that provides {@link StepScope} and {@link JobScope}.
*
* @author Dave Syer
* @author Mahmoud Ben Hassine
*/
@Configuration(proxyBeanMethods = false)
public class ScopeConfiguration {

View File

@@ -43,7 +43,7 @@ public class CoreNamespaceUtils {
private static final String XML_CONFIG_STEP_SCOPE_PROCESSOR_CLASS_NAME = "org.springframework.batch.core.scope.StepScope";
private static final String JAVA_CONFIG_SCOPE_CLASS_NAME = "org.springframework.batch.core.configuration.annotation.ScopeConfiguration";
private static final String JAVA_CONFIG_SCOPE_CLASS_NAME = "org.springframework.batch.core.configuration.support.ScopeConfiguration";
private static final String JOB_SCOPE_PROCESSOR_BEAN_NAME = "org.springframework.batch.core.scope.internalJobScope";

View File

@@ -82,7 +82,7 @@ public class JobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean i
private ExecutionContextSerializer serializer;
private Integer lobType;
private Integer clobType;
private Charset charset = StandardCharsets.UTF_8;
@@ -91,7 +91,7 @@ public class JobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean i
* use for a CLOB
*/
public void setClobType(int type) {
this.lobType = type;
this.clobType = type;
}
/**
@@ -215,8 +215,8 @@ public class JobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean i
() -> "'" + databaseType + "' is an unsupported database type. The supported database types are "
+ StringUtils.arrayToCommaDelimitedString(incrementerFactory.getSupportedIncrementerTypes()));
if (lobType != null) {
Assert.isTrue(isValidTypes(lobType), "lobType must be a value from the java.sql.Types class");
if (clobType != null) {
Assert.isTrue(isValidTypes(clobType), "lobType must be a value from the java.sql.Types class");
}
super.afterPropertiesSet();
@@ -278,8 +278,8 @@ public class JobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean i
}
private int determineClobTypeToUse(String databaseType) throws Exception {
if (lobType != null) {
return lobType;
if (clobType != null) {
return clobType;
}
else {
if (SYBASE == DatabaseType.valueOf(databaseType.toUpperCase())) {

View File

@@ -0,0 +1,187 @@
/*
* Copyright 2022 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
*
* https://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.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import org.mockito.Mockito;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.dao.JdbcJobInstanceDao;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.interceptor.TransactionInterceptor;
/**
* Test class for {@link BatchRegistrar}.
*
* @author Mahmoud Ben Hassine
*/
class BatchRegistrarTests {
@Test
@DisplayName("When no datasource is provided, then an IllegalStateException should be thrown")
void testMissingDataSource() {
Assertions.assertThrows(IllegalStateException.class, new Executable() {
@Override
public void execute() throws Throwable {
new AnnotationConfigApplicationContext(JobConfigurationWithoutDataSource.class);
}
});
}
@Test
@DisplayName("When no transaction manager is provided, then an IllegalStateException should be thrown")
void testMissingTransactionManager() {
Assertions.assertThrows(IllegalStateException.class, new Executable() {
@Override
public void execute() throws Throwable {
new AnnotationConfigApplicationContext(JobConfigurationWithoutTransactionManager.class);
}
});
}
@Test
@DisplayName("When cusotm beans are provided, then no new ones should be created")
void testConfigurationWithUserDefinedBeans() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
JobConfigurationWithUserDefinedInfrastrucutreBeans.class);
Assertions.assertEquals(JobConfigurationWithUserDefinedInfrastrucutreBeans.jobRepository,
context.getBean(JobRepository.class));
Assertions.assertEquals(JobConfigurationWithUserDefinedInfrastrucutreBeans.jobExplorer,
context.getBean(JobExplorer.class));
Assertions.assertEquals(JobConfigurationWithUserDefinedInfrastrucutreBeans.jobLauncher,
context.getBean(JobLauncher.class));
Assertions.assertEquals(JobConfigurationWithUserDefinedInfrastrucutreBeans.jobRegistry,
context.getBean(JobRegistry.class));
}
@Test
@DisplayName("When a datasource and a transaction manager are provided, then they should be set on the job repository")
void testDataSourceAndTransactionManagerSetup() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JobConfiguration.class);
JobRepository jobRepository = context.getBean(JobRepository.class);
JdbcJobInstanceDao jobInstanceDao = (JdbcJobInstanceDao) ReflectionTestUtils.getField(jobRepository,
"jobInstanceDao");
JdbcTemplate jdbcTemplate = (JdbcTemplate) ReflectionTestUtils.getField(jobInstanceDao, "jdbcTemplate");
DataSource dataSource = (DataSource) ReflectionTestUtils.getField(jdbcTemplate, "dataSource");
Assertions.assertEquals(context.getBean(DataSource.class), dataSource);
// TODO assert on other DAOs
PlatformTransactionManager transactionManager = getTransactionManagerSetOnJobRepository(jobRepository);
Assertions.assertEquals(context.getBean(JdbcTransactionManager.class), transactionManager);
}
@Configuration
@EnableBatchProcessing
public static class JobConfigurationWithoutDataSource {
}
@Configuration
@EnableBatchProcessing
public static class JobConfigurationWithoutTransactionManager {
@Bean
public DataSource dataSource() {
return Mockito.mock(DataSource.class);
}
}
@Configuration
@EnableBatchProcessing
public static class JobConfigurationWithUserDefinedInfrastrucutreBeans {
public static JobRepository jobRepository = Mockito.mock(JobRepository.class);
public static JobExplorer jobExplorer = Mockito.mock(JobExplorer.class);
public static JobLauncher jobLauncher = Mockito.mock(JobLauncher.class);
public static JobRegistry jobRegistry = Mockito.mock(JobRegistry.class);
@Bean
public JobRepository jobRepository() {
return jobRepository;
}
@Bean
public JobExplorer jobExplorer() {
return jobExplorer;
}
@Bean
public JobLauncher jobLauncher() {
return jobLauncher;
}
@Bean
public JobRegistry jobRegistry() {
return jobRegistry;
}
}
@Configuration
@EnableBatchProcessing
public static class JobConfiguration {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL)
.addScript("/org/springframework/batch/core/schema-hsqldb.sql").generateUniqueName(true).build();
}
@Bean
public JdbcTransactionManager transactionManager(DataSource dataSource) {
return new JdbcTransactionManager(dataSource);
}
}
private PlatformTransactionManager getTransactionManagerSetOnJobRepository(JobRepository jobRepository) {
Advised target = (Advised) jobRepository; // proxy created by
// AbstractJobRepositoryFactoryBean
Advisor[] advisors = target.getAdvisors();
for (Advisor advisor : advisors) {
if (advisor.getAdvice() instanceof TransactionInterceptor transactionInterceptor) {
return (PlatformTransactionManager) transactionInterceptor.getTransactionManager();
}
}
return null;
}
}

View File

@@ -15,38 +15,22 @@
*/
package org.springframework.batch.core.configuration.annotation;
import org.springframework.batch.core.Step;
import org.springframework.beans.factory.annotation.Autowired;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.util.ClassUtils;
import jakarta.annotation.PostConstruct;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jdbc.support.JdbcTransactionManager;
@Configuration
public class DataSourceConfiguration {
@Autowired
private ResourceLoader resourceLoader;
@PostConstruct
protected void initialize() {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(
resourceLoader.getResource(ClassUtils.addResourcePathToPackagePath(Step.class, "schema-hsqldb.sql")));
populator.setContinueOnError(true);
DatabasePopulatorUtils.execute(populator, dataSource());
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().generateUniqueName(true).build();
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL)
.addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql")
.addScript("/org/springframework/batch/core/schema-hsqldb.sql").generateUniqueName(true).build();
}
@Bean

View File

@@ -61,11 +61,6 @@ public class JobBuilderConfigurationTests {
testJob(BatchStatus.COMPLETED, 2, TestConfiguration.class);
}
@Test
void testConfigurerAsConfiguration() throws Exception {
testJob(BatchStatus.COMPLETED, 1, TestConfigurer.class);
}
@Test
void testConfigurerAsBean() throws Exception {
testJob(BatchStatus.COMPLETED, 1, BeansConfigurer.class);
@@ -76,11 +71,6 @@ public class JobBuilderConfigurationTests {
testJob("testJob", BatchStatus.COMPLETED, 2, TestConfiguration.class, AnotherConfiguration.class);
}
@Test
void testTwoConfigurationsAndConfigurer() throws Exception {
testJob("testJob", BatchStatus.COMPLETED, 2, TestConfiguration.class, TestConfigurer.class);
}
@Test
void testTwoConfigurationsAndBeansConfigurer() throws Exception {
testJob("testJob", BatchStatus.COMPLETED, 2, TestConfiguration.class, BeansConfigurer.class);
@@ -174,36 +164,6 @@ public class JobBuilderConfigurationTests {
}
@Configuration
@EnableBatchProcessing
@Import(DataSourceConfiguration.class)
public static class TestConfigurer extends DefaultBatchConfigurer {
public TestConfigurer(DataSource dataSource) {
super(dataSource);
}
@Bean
public Job testConfigurerJob(JobRepository jobRepository) throws Exception {
SimpleJobBuilder builder = new JobBuilder("configurer", jobRepository).start(step1());
return builder.build();
}
@Bean
protected Step step1() throws Exception {
AbstractStep step = new AbstractStep("step1") {
@Override
protected void doExecute(StepExecution stepExecution) throws Exception {
stepExecution.setExitStatus(ExitStatus.COMPLETED);
stepExecution.setStatus(BatchStatus.COMPLETED);
}
};
step.setJobRepository(getJobRepository());
return step;
}
}
@Configuration
@EnableBatchProcessing
@Import(DataSourceConfiguration.class)
@@ -230,12 +190,6 @@ public class JobBuilderConfigurationTests {
}, this.transactionManager).build();
}
@Bean
@Autowired
protected BatchConfigurer configurer(DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource);
}
}
@Configuration

View File

@@ -40,6 +40,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;

View File

@@ -1,109 +0,0 @@
/*
* Copyright 2018-2022 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
*
* https://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.jupiter.api.Test;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.test.util.AopTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Mahmoud Ben Hassine
*/
class TransactionManagerConfigurationWithBatchConfigurerTests extends TransactionManagerConfigurationTests {
@Test
void testConfigurationWithDataSourceAndNoTransactionManager() throws Exception {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(
BatchConfigurationWithDataSourceAndNoTransactionManager.class);
DefaultBatchConfigurer batchConfigurer = applicationContext.getBean(DefaultBatchConfigurer.class);
PlatformTransactionManager platformTransactionManager = batchConfigurer.getTransactionManager();
assertTrue(platformTransactionManager instanceof JdbcTransactionManager);
JdbcTransactionManager JdbcTransactionManager = AopTestUtils.getTargetObject(platformTransactionManager);
assertEquals(applicationContext.getBean(DataSource.class), JdbcTransactionManager.getDataSource());
assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)),
platformTransactionManager);
}
@Test
void testConfigurationWithDataSourceAndTransactionManager() throws Exception {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(
BatchConfigurationWithDataSourceAndTransactionManager.class);
DefaultBatchConfigurer batchConfigurer = applicationContext.getBean(DefaultBatchConfigurer.class);
PlatformTransactionManager platformTransactionManager = batchConfigurer.getTransactionManager();
assertSame(transactionManager, platformTransactionManager);
assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)),
transactionManager);
}
@Configuration
@EnableBatchProcessing
public static class BatchConfigurationWithDataSourceAndNoTransactionManager {
@Bean
public DataSource dataSource() {
return createDataSource();
}
@Bean
public BatchConfigurer batchConfigurer(DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource);
}
}
@Configuration
@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

@@ -1,160 +0,0 @@
/*
* Copyright 2018-2022 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
*
* https://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.jupiter.api.Test;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Mahmoud Ben Hassine
*/
class TransactionManagerConfigurationWithoutBatchConfigurerTests extends TransactionManagerConfigurationTests {
@Test
void testConfigurationWithNoDataSourceAndNoTransactionManager() {
assertThrows(BeanCreationException.class, () -> new AnnotationConfigApplicationContext(
BatchConfigurationWithNoDataSourceAndNoTransactionManager.class));
}
@Test
void testConfigurationWithNoDataSourceAndTransactionManager() {
assertThrows(BeanCreationException.class, () -> new AnnotationConfigApplicationContext(
BatchConfigurationWithNoDataSourceAndTransactionManager.class));
}
@Test
void testConfigurationWithDataSourceAndNoTransactionManager() throws Exception {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(
BatchConfigurationWithDataSourceAndNoTransactionManager.class);
PlatformTransactionManager platformTransactionManager = getTransactionManagerSetOnJobRepository(
applicationContext.getBean(JobRepository.class));
assertTrue(platformTransactionManager instanceof JdbcTransactionManager);
JdbcTransactionManager JdbcTransactionManager = (JdbcTransactionManager) platformTransactionManager;
assertEquals(applicationContext.getBean(DataSource.class), JdbcTransactionManager.getDataSource());
}
@Test
void testConfigurationWithDataSourceAndOneTransactionManager() throws Exception {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(
BatchConfigurationWithDataSourceAndOneTransactionManager.class);
PlatformTransactionManager platformTransactionManager = applicationContext
.getBean(PlatformTransactionManager.class);
assertSame(transactionManager, platformTransactionManager);
// In this case, the supplied transaction manager won't be used by batch and a
// JdbcTransactionManager will be used instead.
// The user has to provide a custom BatchConfigurer.
assertTrue(getTransactionManagerSetOnJobRepository(
applicationContext.getBean(JobRepository.class)) instanceof JdbcTransactionManager);
}
@Test
void testConfigurationWithDataSourceAndMultipleTransactionManagers() throws Exception {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(
BatchConfigurationWithDataSourceAndMultipleTransactionManagers.class);
PlatformTransactionManager platformTransactionManager = applicationContext
.getBean(PlatformTransactionManager.class);
assertSame(transactionManager2, platformTransactionManager);
// In this case, the supplied primary transaction manager won't be used by batch
// and a JdbcTransactionManager will be used instead.
// The user has to provide a custom BatchConfigurer.
assertTrue(getTransactionManagerSetOnJobRepository(
applicationContext.getBean(JobRepository.class)) instanceof JdbcTransactionManager);
}
@Configuration
@EnableBatchProcessing
public static class BatchConfigurationWithNoDataSourceAndNoTransactionManager {
}
@Configuration
@EnableBatchProcessing
public static class BatchConfigurationWithNoDataSourceAndTransactionManager {
@Bean
public PlatformTransactionManager transactionManager() {
return transactionManager;
}
}
@Configuration
@EnableBatchProcessing
public static class BatchConfigurationWithDataSourceAndNoTransactionManager {
@Bean
public DataSource dataSource() {
return createDataSource();
}
}
@Configuration
@EnableBatchProcessing
public static class BatchConfigurationWithDataSourceAndOneTransactionManager {
@Bean
public DataSource dataSource() {
return createDataSource();
}
@Bean
public PlatformTransactionManager transactionManager() {
return transactionManager;
}
}
@Configuration
@EnableBatchProcessing
public static class BatchConfigurationWithDataSourceAndMultipleTransactionManagers {
@Bean
public DataSource dataSource() {
return createDataSource();
}
@Bean
public PlatformTransactionManager transactionManager() {
return transactionManager;
}
@Primary
@Bean
public PlatformTransactionManager transactionManager2() {
return transactionManager2;
}
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright 2022 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
*
* https://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.support;
import java.util.Map;
import javax.sql.DataSource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.xml.DummyJobRepository;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
/**
* @author Mahmoud Ben Hassine
*/
class DefaultBatchConfigurationTests {
@Test
void testDefaultConfiguration() throws Exception {
// given
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyJobConfiguration.class);
Job job = context.getBean(Job.class);
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
// when
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
// then
Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
}
@Test
void testConfigurationWithoutDataSource() {
Assertions.assertThrows(BeanCreationException.class, new Executable() {
@Override
public void execute() throws Throwable {
new AnnotationConfigApplicationContext(MyJobConfigurationWithoutDataSource.class);
}
});
}
@Test
void testConfigurationWithoutTransactionManager() {
Assertions.assertThrows(BeanCreationException.class, new Executable() {
@Override
public void execute() throws Throwable {
new AnnotationConfigApplicationContext(MyJobConfigurationWithoutTransactionManager.class);
}
});
}
@Test
void testConfigurationWithCustomInfrastructureBean() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
MyJobConfigurationWithCustomInfrastructureBean.class);
Map<String, JobRepository> jobRepositories = context.getBeansOfType(JobRepository.class);
Assertions.assertEquals(1, jobRepositories.size());
JobRepository jobRepository = jobRepositories.entrySet().iterator().next().getValue();
Assertions.assertInstanceOf(DummyJobRepository.class, jobRepository);
}
@Configuration
static class MyJobConfigurationWithoutDataSource extends DefaultBatchConfiguration {
}
@Configuration
static class MyJobConfigurationWithoutTransactionManager extends DefaultBatchConfiguration {
}
@Configuration
static class MyJobConfiguration extends DefaultBatchConfiguration {
@Bean
public Step myStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
Tasklet myTasklet = (contribution, chunkContext) -> {
System.out.println("Hello world");
return RepeatStatus.FINISHED;
};
return new StepBuilder("myStep", jobRepository).tasklet(myTasklet, transactionManager).build();
}
@Bean
public Job job(JobRepository jobRepository, Step myStep) {
return new JobBuilder("job", jobRepository).start(myStep).build();
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL)
.addScript("/org/springframework/batch/core/schema-hsqldb.sql").generateUniqueName(true).build();
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new JdbcTransactionManager(dataSource);
}
}
@Configuration
static class MyJobConfigurationWithCustomInfrastructureBean extends MyJobConfiguration {
@Bean
public JobRepository jobRepository() {
return new DummyJobRepository();
}
}
}

View File

@@ -18,6 +18,8 @@ package org.springframework.batch.core.explore.support;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.junit.jupiter.api.Test;
import test.jdbc.datasource.DataSourceInitializer;
@@ -52,6 +54,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -127,6 +130,11 @@ class SimpleJobExplorerIntegrationTests {
return dataSourceInitializer;
}
@Bean
public JdbcTransactionManager transactionManager(DataSource dataSource) {
return new JdbcTransactionManager(dataSource);
}
@Bean
public Job job(JobRepository jobRepository) {
return new JobBuilder("job", jobRepository).start(dummyStep()).build();

View File

@@ -21,6 +21,7 @@ import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.batch.core.BatchStatus;
@@ -29,7 +30,6 @@ import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.builder.JobBuilder;
@@ -48,6 +48,7 @@ import org.springframework.context.annotation.Import;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.jdbc.datasource.embedded.ConnectionProperties;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseConfigurer;
@@ -67,6 +68,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
* @author Michael Minella
* @author Mahmoud Ben Hassine
*/
// FIXME incorrect configuration of JobLauncher. This should be failing with v4.
@Disabled
@SpringJUnitConfig(classes = ConcurrentTransactionTests.ConcurrentJobConfiguration.class)
class ConcurrentTransactionTests {
@@ -82,17 +85,13 @@ class ConcurrentTransactionTests {
JobExecution jobExecution = jobLauncher.run(concurrentJob, new JobParameters());
assertEquals(jobExecution.getStatus(), BatchStatus.COMPLETED);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
}
@Configuration
@EnableBatchProcessing
@Import(DataSourceConfiguration.class)
public static class ConcurrentJobConfiguration extends DefaultBatchConfigurer {
public ConcurrentJobConfiguration(DataSource dataSource, PlatformTransactionManager transactionManager) {
super(dataSource, transactionManager);
}
public static class ConcurrentJobConfiguration {
@Bean
public TaskExecutor taskExecutor() {
@@ -100,7 +99,7 @@ class ConcurrentTransactionTests {
}
@Bean
public Flow flow(JobRepository jobRepository) {
public Flow flow(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new FlowBuilder<Flow>("flow")
.start(new StepBuilder("flow.step1", jobRepository).tasklet(new Tasklet() {
@Nullable
@@ -109,19 +108,19 @@ class ConcurrentTransactionTests {
throws Exception {
return RepeatStatus.FINISHED;
}
}, getTransactionManager()).build())
.next(new StepBuilder("flow.step2", jobRepository).tasklet(new Tasklet() {
}, transactionManager).build())
.next(new StepBuilder("flow.step2").repository(jobRepository).tasklet(new Tasklet() {
@Nullable
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext)
throws Exception {
return RepeatStatus.FINISHED;
}
}, getTransactionManager()).build()).build();
}, transactionManager).build()).build();
}
@Bean
public Step firstStep(JobRepository jobRepository) {
public Step firstStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("firstStep", jobRepository).tasklet(new Tasklet() {
@Nullable
@Override
@@ -129,11 +128,11 @@ class ConcurrentTransactionTests {
System.out.println(">> Beginning concurrent job test");
return RepeatStatus.FINISHED;
}
}, getTransactionManager()).build();
}, transactionManager).build();
}
@Bean
public Step lastStep(JobRepository jobRepository) {
public Step lastStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("lastStep", jobRepository).tasklet(new Tasklet() {
@Nullable
@Override
@@ -141,27 +140,31 @@ class ConcurrentTransactionTests {
System.out.println(">> Ending concurrent job test");
return RepeatStatus.FINISHED;
}
}, getTransactionManager()).build();
}, transactionManager).build();
}
@Bean
public Job concurrentJob(JobRepository jobRepository) {
Flow splitFlow = new FlowBuilder<Flow>("splitflow").split(new SimpleAsyncTaskExecutor())
.add(flow(jobRepository), flow(jobRepository), flow(jobRepository), flow(jobRepository),
flow(jobRepository), flow(jobRepository), flow(jobRepository))
public Job concurrentJob(JobRepository jobRepository, PlatformTransactionManager transactionManager,
TaskExecutor taskExecutor) {
Flow splitFlow = new FlowBuilder<Flow>("splitflow").split(taskExecutor)
.add(flow(jobRepository, transactionManager), flow(jobRepository, transactionManager),
flow(jobRepository, transactionManager), flow(jobRepository, transactionManager),
flow(jobRepository, transactionManager), flow(jobRepository, transactionManager),
flow(jobRepository, transactionManager))
.build();
return new JobBuilder("concurrentJob", jobRepository).start(firstStep(jobRepository))
return new JobBuilder("concurrentJob", jobRepository).start(firstStep(jobRepository, transactionManager))
.next(new StepBuilder("splitFlowStep", jobRepository).flow(splitFlow).build())
.next(lastStep(jobRepository)).build();
.next(lastStep(jobRepository, transactionManager)).build();
}
@Override
protected JobRepository createJobRepository() throws Exception {
@Bean
public JobRepository jobRepository(DataSource dataSource, PlatformTransactionManager transactionManager)
throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(getDataSource());
factory.setDataSource(dataSource);
factory.setIsolationLevelForCreate(Isolation.READ_COMMITTED);
factory.setTransactionManager(getTransactionManager());
factory.setTransactionManager(transactionManager);
factory.afterPropertiesSet();
return factory.getObject();
}
@@ -183,7 +186,7 @@ class ConcurrentTransactionTests {
* @return
*/
@Bean
DataSource dataSource() {
public DataSource dataSource() {
ResourceLoader defaultResourceLoader = new DefaultResourceLoader();
EmbeddedDatabaseFactory embeddedDatabaseFactory = new EmbeddedDatabaseFactory();
embeddedDatabaseFactory.setDatabaseConfigurer(new EmbeddedDatabaseConfigurer() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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
@@ -18,6 +18,7 @@ import org.springframework.batch.core.configuration.annotation.EnableBatchProces
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.support.JdbcTransactionManager;
/**
* @author Gunnar Hillert
@@ -35,4 +36,9 @@ public class JobLauncherParserTestsConfiguration {
.addScript("/org/springframework/batch/core/schema-hsqldb.sql").generateUniqueName(true).build();
}
@Bean
public JdbcTransactionManager transactionManager(DataSource dataSource) {
return new JdbcTransactionManager(dataSource);
}
}

View File

@@ -34,6 +34,7 @@ import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
@@ -241,6 +242,11 @@ class RemotePartitioningManagerStepBuilderTests {
.addScript("/org/springframework/batch/core/schema-hsqldb.sql").generateUniqueName(true).build();
}
@Bean
public JdbcTransactionManager transactionManager(DataSource dataSource) {
return new JdbcTransactionManager(dataSource);
}
}
}