From f7fcfaa4fdb1f762a3bc16c30750d646dc52a6ed Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 22 May 2025 11:38:29 +0200 Subject: [PATCH] Improve batch infrastructure configuration Before this commit, EnableBatchProcessing was tied to a JDBC infrastructure. Therefore, it was impossible to use a non-JDBC job repository with that annotation. This commit removes the dependency to a JDBC infrastructure from EnableBatchProcessing and introduces new annotations to configure specific job repository implementations. It also updates the programmatic way of configuring infrastructure beans with a base configuration class for each supported job repository implementation. NB: The XML namespace was not changed accordingly as the XSD will not be updated starting from v6. Resolves #4718 --- .../annotation/BatchRegistrar.java | 119 ++++++-- .../annotation/EnableBatchProcessing.java | 119 +------- .../annotation/EnableJdbcJobRepository.java | 128 +++++++++ .../annotation/EnableMongoJobRepository.java | 63 +++++ .../support/DefaultBatchConfiguration.java | 267 +++--------------- .../JdbcDefaultBatchConfiguration.java | 262 +++++++++++++++++ .../MongoDefaultBatchConfiguration.java | 122 ++++++++ .../annotation/BatchRegistrarTests.java | 37 +-- .../DefaultBatchConfigurationTests.java | 22 -- ...GenericApplicationContextFactoryTests.java | 12 - .../SimpleJobExplorerIntegrationTests.java | 3 + .../Db2JobRepositoryIntegrationTests.java | 2 + .../DerbyJobRepositoryIntegrationTests.java | 2 + ...lityModeJobRepositoryIntegrationTests.java | 2 + .../H2JobRepositoryIntegrationTests.java | 2 + .../HANAJobRepositoryIntegrationTests.java | 2 + .../HSQLDBJobRepositoryIntegrationTests.java | 2 + .../MariaDBJobRepositoryIntegrationTests.java | 2 + ...ySQLJdbcJobRepositoryIntegrationTests.java | 2 + .../MySQLJobRepositoryIntegrationTests.java | 2 + .../OracleJobRepositoryIntegrationTests.java | 2 + ...stgreSQLJobRepositoryIntegrationTests.java | 2 + ...QLServerJobRepositoryIntegrationTests.java | 2 + .../SQLiteJobRepositoryIntegrationTests.java | 2 + .../SybaseJobRepositoryIntegrationTests.java | 2 + .../samples/amqp/AmqpJobConfiguration.java | 2 + .../HelloWorldJobConfiguration.java | 3 +- .../samples/jpa/JpaJobConfiguration.java | 5 +- .../jpa/JpaRepositoryJobConfiguration.java | 5 +- .../aggregating/ManagerConfiguration.java | 2 + .../aggregating/WorkerConfiguration.java | 2 + .../remote/polling/ManagerConfiguration.java | 2 + .../remote/polling/WorkerConfiguration.java | 2 + 33 files changed, 773 insertions(+), 432 deletions(-) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableJdbcJobRepository.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableMongoJobRepository.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java index 1c3164da6..fe836c8fa 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java @@ -25,14 +25,16 @@ import org.springframework.batch.core.configuration.support.DefaultJobLoader; import org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton; import org.springframework.batch.core.configuration.support.MapJobRegistry; import org.springframework.batch.core.launch.support.JobOperatorFactoryBean; -import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher; import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.MongoJobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.ResourcelessJobRepository; 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.transaction.annotation.Isolation; import org.springframework.util.StopWatch; import org.springframework.util.StringUtils; @@ -52,6 +54,8 @@ class BatchRegistrar implements ImportBeanDefinitionRegistrar { private static final String JOB_REPOSITORY = "jobRepository"; + private static final String JOB_OPERATOR = "jobOperator"; + private static final String JOB_REGISTRY = "jobRegistry"; private static final String JOB_LOADER = "jobLoader"; @@ -64,7 +68,7 @@ class BatchRegistrar implements ImportBeanDefinitionRegistrar { EnableBatchProcessing batchAnnotation = importingClassMetadata.getAnnotations() .get(EnableBatchProcessing.class) .synthesize(); - registerJobRepository(registry, batchAnnotation); + registerJobRepository(registry, importingClassMetadata); registerJobRegistry(registry); registerJobRegistrySmartInitializingSingleton(registry); registerJobOperator(registry, batchAnnotation); @@ -82,65 +86,126 @@ class BatchRegistrar implements ImportBeanDefinitionRegistrar { } } - private void registerJobRepository(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { + private void registerJobRepository(BeanDefinitionRegistry registry, AnnotationMetadata importingClassMetadata) { if (registry.containsBeanDefinition(JOB_REPOSITORY)) { LOGGER.info("Bean jobRepository already defined in the application context, skipping" + " the registration of a jobRepository"); return; } + if (importingClassMetadata.hasAnnotation(EnableJdbcJobRepository.class.getName())) { + registerJdbcJobRepository(registry, importingClassMetadata); + } + else { + if (importingClassMetadata.hasAnnotation(EnableMongoJobRepository.class.getName())) { + registerMongoJobRepository(registry, importingClassMetadata); + } + else { + registerDefaultJobRepository(registry); + } + } + } + + private void registerJdbcJobRepository(BeanDefinitionRegistry registry, AnnotationMetadata importingClassMetadata) { + EnableJdbcJobRepository jdbcJobRepositoryAnnotation = importingClassMetadata.getAnnotations() + .get(EnableJdbcJobRepository.class) + .synthesize(); BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder .genericBeanDefinition(JdbcJobRepositoryFactoryBean.class); // set mandatory properties - String dataSourceRef = batchAnnotation.dataSourceRef(); + String dataSourceRef = jdbcJobRepositoryAnnotation.dataSourceRef(); beanDefinitionBuilder.addPropertyReference("dataSource", dataSourceRef); - String transactionManagerRef = batchAnnotation.transactionManagerRef(); + String transactionManagerRef = jdbcJobRepositoryAnnotation.transactionManagerRef(); beanDefinitionBuilder.addPropertyReference("transactionManager", transactionManagerRef); // set optional properties - String executionContextSerializerRef = batchAnnotation.executionContextSerializerRef(); + String executionContextSerializerRef = jdbcJobRepositoryAnnotation.executionContextSerializerRef(); if (registry.containsBeanDefinition(executionContextSerializerRef)) { beanDefinitionBuilder.addPropertyReference("serializer", executionContextSerializerRef); } - String conversionServiceRef = batchAnnotation.conversionServiceRef(); + String conversionServiceRef = jdbcJobRepositoryAnnotation.conversionServiceRef(); if (registry.containsBeanDefinition(conversionServiceRef)) { beanDefinitionBuilder.addPropertyReference("conversionService", conversionServiceRef); } - String incrementerFactoryRef = batchAnnotation.incrementerFactoryRef(); + String incrementerFactoryRef = jdbcJobRepositoryAnnotation.incrementerFactoryRef(); if (registry.containsBeanDefinition(incrementerFactoryRef)) { beanDefinitionBuilder.addPropertyReference("incrementerFactory", incrementerFactoryRef); } - String jobKeyGeneratorRef = batchAnnotation.jobKeyGeneratorRef(); - if (registry.containsBeanDefinition(jobKeyGeneratorRef)) { - beanDefinitionBuilder.addPropertyReference("jobKeyGenerator", jobKeyGeneratorRef); - } - - String charset = batchAnnotation.charset(); + String charset = jdbcJobRepositoryAnnotation.charset(); if (charset != null) { beanDefinitionBuilder.addPropertyValue("charset", Charset.forName(charset)); } - String tablePrefix = batchAnnotation.tablePrefix(); + String tablePrefix = jdbcJobRepositoryAnnotation.tablePrefix(); if (tablePrefix != null) { beanDefinitionBuilder.addPropertyValue("tablePrefix", tablePrefix); } - String isolationLevelForCreate = batchAnnotation.isolationLevelForCreate(); - if (isolationLevelForCreate != null) { - beanDefinitionBuilder.addPropertyValue("isolationLevelForCreate", isolationLevelForCreate); - } - - String databaseType = batchAnnotation.databaseType(); + String databaseType = jdbcJobRepositoryAnnotation.databaseType(); if (StringUtils.hasText(databaseType)) { beanDefinitionBuilder.addPropertyValue("databaseType", databaseType); } - beanDefinitionBuilder.addPropertyValue("maxVarCharLength", batchAnnotation.maxVarCharLength()); - beanDefinitionBuilder.addPropertyValue("clobType", batchAnnotation.clobType()); + String jdbcOperationsRef = jdbcJobRepositoryAnnotation.jdbcOperationsRef(); + if (registry.containsBeanDefinition(jdbcOperationsRef)) { + beanDefinitionBuilder.addPropertyReference("jdbcOperations", jdbcOperationsRef); + } + + beanDefinitionBuilder.addPropertyValue("maxVarCharLength", jdbcJobRepositoryAnnotation.maxVarCharLength()); + beanDefinitionBuilder.addPropertyValue("clobType", jdbcJobRepositoryAnnotation.clobType()); + beanDefinitionBuilder.addPropertyValue("validateTransactionState", + jdbcJobRepositoryAnnotation.validateTransactionState()); + + Isolation isolationLevelForCreate = jdbcJobRepositoryAnnotation.isolationLevelForCreate(); + if (isolationLevelForCreate != null) { + beanDefinitionBuilder.addPropertyValue("isolationLevelForCreateEnum", isolationLevelForCreate); + } + + String jobKeyGeneratorRef = jdbcJobRepositoryAnnotation.jobKeyGeneratorRef(); + if (registry.containsBeanDefinition(jobKeyGeneratorRef)) { + beanDefinitionBuilder.addPropertyReference("jobKeyGenerator", jobKeyGeneratorRef); + } + + registry.registerBeanDefinition(JOB_REPOSITORY, beanDefinitionBuilder.getBeanDefinition()); + } + + private void registerMongoJobRepository(BeanDefinitionRegistry registry, + AnnotationMetadata importingClassMetadata) { + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder + .genericBeanDefinition(MongoJobRepositoryFactoryBean.class); + EnableMongoJobRepository mongoJobRepositoryAnnotation = importingClassMetadata.getAnnotations() + .get(EnableMongoJobRepository.class) + .synthesize(); + String mongoOperationsRef = mongoJobRepositoryAnnotation.mongoOperationsRef(); + if (registry.containsBeanDefinition(mongoOperationsRef)) { + beanDefinitionBuilder.addPropertyReference("mongoOperations", mongoOperationsRef); + } + String transactionManagerRef = mongoJobRepositoryAnnotation.transactionManagerRef(); + if (registry.containsBeanDefinition(transactionManagerRef)) { + beanDefinitionBuilder.addPropertyReference("transactionManager", transactionManagerRef); + } + Isolation isolationLevelForCreate = mongoJobRepositoryAnnotation.isolationLevelForCreate(); + if (isolationLevelForCreate != null) { + beanDefinitionBuilder.addPropertyValue("isolationLevelForCreate", isolationLevelForCreate); + } + + String jobKeyGeneratorRef = mongoJobRepositoryAnnotation.jobKeyGeneratorRef(); + if (registry.containsBeanDefinition(jobKeyGeneratorRef)) { + beanDefinitionBuilder.addPropertyReference("jobKeyGenerator", jobKeyGeneratorRef); + } + beanDefinitionBuilder.addPropertyValue("validateTransactionState", + mongoJobRepositoryAnnotation.validateTransactionState()); + + registry.registerBeanDefinition(JOB_REPOSITORY, beanDefinitionBuilder.getBeanDefinition()); + } + + private void registerDefaultJobRepository(BeanDefinitionRegistry registry) { + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder + .genericBeanDefinition(ResourcelessJobRepository.class); registry.registerBeanDefinition(JOB_REPOSITORY, beanDefinitionBuilder.getBeanDefinition()); } @@ -171,7 +236,7 @@ class BatchRegistrar implements ImportBeanDefinitionRegistrar { } private void registerJobOperator(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { - if (registry.containsBeanDefinition("jobOperator")) { + if (registry.containsBeanDefinition(JOB_OPERATOR)) { LOGGER.info("Bean jobOperator already defined in the application context, skipping" + " the registration of a jobOperator"); return; @@ -186,12 +251,16 @@ class BatchRegistrar implements ImportBeanDefinitionRegistrar { beanDefinitionBuilder.addPropertyReference(JOB_REGISTRY, JOB_REGISTRY); // set optional properties + String taskExecutorRef = batchAnnotation.taskExecutorRef(); + if (registry.containsBeanDefinition(taskExecutorRef)) { + beanDefinitionBuilder.addPropertyReference("taskExecutor", taskExecutorRef); + } String jobParametersConverterRef = batchAnnotation.jobParametersConverterRef(); if (registry.containsBeanDefinition(jobParametersConverterRef)) { beanDefinitionBuilder.addPropertyReference("jobParametersConverter", jobParametersConverterRef); } - registry.registerBeanDefinition("jobOperator", beanDefinitionBuilder.getBeanDefinition()); + registry.registerBeanDefinition(JOB_OPERATOR, beanDefinitionBuilder.getBeanDefinition()); } private void registerAutomaticJobRegistrar(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java index 4b653530d..61940215b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java @@ -15,27 +15,15 @@ */ package org.springframework.batch.core.configuration.annotation; -import java.lang.annotation.Documented; -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.converter.JobParametersConverter; -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.batch.support.DatabaseType; import org.springframework.context.annotation.Import; -import org.springframework.transaction.PlatformTransactionManager; + +import java.lang.annotation.*; /** *

@@ -67,9 +55,10 @@ import org.springframework.transaction.PlatformTransactionManager; * } * * - * This annotation configures JDBC-based Batch infrastructure beans, so you must provide a - * {@link DataSource} and a {@link PlatformTransactionManager} as beans in the application - * context. + * By default,this annotation configures a resouceless batch infrastructure (ie based on a + * {@link org.springframework.batch.core.repository.support.ResourcelessJobRepository} and + * a + * {@link org.springframework.batch.support.transaction.ResourcelessTransactionManager}). * * Note that only one of your configuration classes needs to have the * @EnableBatchProcessing annotation. Once you have an @@ -83,8 +72,6 @@ import org.springframework.transaction.PlatformTransactionManager; *