diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BasicBatchConfigurer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BasicBatchConfigurer.java index 1f0b96005c..8917fcc008 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BasicBatchConfigurer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BasicBatchConfigurer.java @@ -17,12 +17,8 @@ package org.springframework.boot.autoconfigure.batch; import javax.annotation.PostConstruct; -import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import org.springframework.batch.core.configuration.annotation.BatchConfigurer; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.explore.support.JobExplorerFactoryBean; @@ -32,7 +28,6 @@ import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; import org.springframework.jdbc.datasource.DataSourceTransactionManager; -import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.util.StringUtils; @@ -42,17 +37,14 @@ import org.springframework.util.StringUtils; * @author Dave Syer * @author Andy Wilkinson * @author Kazuki Shimizu + * @author Stephane Nicoll */ public class BasicBatchConfigurer implements BatchConfigurer { - private static final Log logger = LogFactory.getLog(BasicBatchConfigurer.class); - private final BatchProperties properties; private final DataSource dataSource; - private final EntityManagerFactory entityManagerFactory; - private PlatformTransactionManager transactionManager; private final TransactionManagerCustomizers transactionManagerCustomizers; @@ -72,22 +64,7 @@ public class BasicBatchConfigurer implements BatchConfigurer { */ protected BasicBatchConfigurer(BatchProperties properties, DataSource dataSource, TransactionManagerCustomizers transactionManagerCustomizers) { - this(properties, dataSource, null, transactionManagerCustomizers); - } - - /** - * Create a new {@link BasicBatchConfigurer} instance. - * @param properties the batch properties - * @param dataSource the underlying data source - * @param entityManagerFactory the entity manager factory (or {@code null}) - * @param transactionManagerCustomizers transaction manager customizers (or - * {@code null}) - */ - protected BasicBatchConfigurer(BatchProperties properties, DataSource dataSource, - EntityManagerFactory entityManagerFactory, - TransactionManagerCustomizers transactionManagerCustomizers) { this.properties = properties; - this.entityManagerFactory = entityManagerFactory; this.dataSource = dataSource; this.transactionManagerCustomizers = transactionManagerCustomizers; } @@ -115,7 +92,7 @@ public class BasicBatchConfigurer implements BatchConfigurer { @PostConstruct public void initialize() { try { - this.transactionManager = createTransactionManager(); + this.transactionManager = buildTransactionManager(); this.jobRepository = createJobRepository(); this.jobLauncher = createJobLauncher(); this.jobExplorer = createJobExplorer(); @@ -146,10 +123,9 @@ public class BasicBatchConfigurer implements BatchConfigurer { protected JobRepository createJobRepository() throws Exception { JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); factory.setDataSource(this.dataSource); - if (this.entityManagerFactory != null) { - logger.warn( - "JPA does not support custom isolation levels, so locks may not be taken when launching Jobs"); - factory.setIsolationLevelForCreate("ISOLATION_DEFAULT"); + String isolationLevel = determineIsolationLevel(); + if (isolationLevel != null) { + factory.setIsolationLevelForCreate(isolationLevel); } String tablePrefix = this.properties.getTablePrefix(); if (StringUtils.hasText(tablePrefix)) { @@ -160,19 +136,24 @@ public class BasicBatchConfigurer implements BatchConfigurer { return factory.getObject(); } + /** + * Determine the isolation level for create* operation of the {@link JobRepository}. + * @return the isolation level or {@code null} to use the default + */ + protected String determineIsolationLevel() { + return null; + } + protected PlatformTransactionManager createTransactionManager() { - PlatformTransactionManager transactionManager = createAppropriateTransactionManager(); + return new DataSourceTransactionManager(this.dataSource); + } + + private PlatformTransactionManager buildTransactionManager() { + PlatformTransactionManager transactionManager = createTransactionManager(); if (this.transactionManagerCustomizers != null) { this.transactionManagerCustomizers.customize(transactionManager); } return transactionManager; } - private PlatformTransactionManager createAppropriateTransactionManager() { - if (this.entityManagerFactory != null) { - return new JpaTransactionManager(this.entityManagerFactory); - } - return new DataSourceTransactionManager(this.dataSource); - } - } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java index daf4b467eb..38cead5f62 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java @@ -16,14 +16,11 @@ package org.springframework.boot.autoconfigure.batch; -import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import org.springframework.batch.core.configuration.ListableJobLocator; -import org.springframework.batch.core.configuration.annotation.BatchConfigurer; import org.springframework.batch.core.converter.JobParametersConverter; 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.JobOperator; import org.springframework.batch.core.launch.support.SimpleJobOperator; @@ -37,13 +34,12 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; -import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.io.ResourceLoader; import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.transaction.PlatformTransactionManager; import org.springframework.util.StringUtils; /** @@ -66,6 +62,7 @@ import org.springframework.util.StringUtils; @AutoConfigureAfter(HibernateJpaAutoConfiguration.class) @ConditionalOnBean(JobLauncher.class) @EnableConfigurationProperties(BatchProperties.class) +@Import(BatchConfigurerConfiguration.class) public class BatchAutoConfiguration { private final BatchProperties properties; @@ -106,20 +103,6 @@ public class BatchAutoConfiguration { return new JobExecutionExitCodeGenerator(); } - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(DataSource.class) - public JobExplorer jobExplorer(DataSource dataSource) throws Exception { - JobExplorerFactoryBean factory = new JobExplorerFactoryBean(); - factory.setDataSource(dataSource); - String tablePrefix = this.properties.getTablePrefix(); - if (StringUtils.hasText(tablePrefix)) { - factory.setTablePrefix(tablePrefix); - } - factory.afterPropertiesSet(); - return factory.getObject(); - } - @Bean @ConditionalOnMissingBean(JobOperator.class) public SimpleJobOperator jobOperator(JobExplorer jobExplorer, JobLauncher jobLauncher, @@ -136,38 +119,4 @@ public class BatchAutoConfiguration { return factory; } - @EnableConfigurationProperties(BatchProperties.class) - @ConditionalOnClass(value = PlatformTransactionManager.class, name = "javax.persistence.EntityManagerFactory") - @ConditionalOnMissingBean(BatchConfigurer.class) - @Configuration - protected static class JpaBatchConfiguration { - - private final BatchProperties properties; - - protected JpaBatchConfiguration(BatchProperties properties) { - this.properties = properties; - } - - // The EntityManagerFactory may not be discoverable by type when this condition - // is evaluated, so we need a well-known bean name. This is the one used by Spring - // Boot in the JPA auto configuration. - @Bean - @ConditionalOnBean(name = "entityManagerFactory") - public BasicBatchConfigurer jpaBatchConfigurer(DataSource dataSource, - EntityManagerFactory entityManagerFactory, - ObjectProvider transactionManagerCustomizers) { - return new BasicBatchConfigurer(this.properties, dataSource, - entityManagerFactory, transactionManagerCustomizers.getIfAvailable()); - } - - @Bean - @ConditionalOnMissingBean(name = "entityManagerFactory") - public BasicBatchConfigurer basicBatchConfigurer(DataSource dataSource, - ObjectProvider transactionManagerCustomizers) { - return new BasicBatchConfigurer(this.properties, dataSource, - transactionManagerCustomizers.getIfAvailable()); - } - - } - } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchConfigurerConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchConfigurerConfiguration.java new file mode 100644 index 0000000000..68f47525c4 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchConfigurerConfiguration.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.batch; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +import org.springframework.batch.core.configuration.annotation.BatchConfigurer; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * Provide a {@link BatchConfigurer} according to the current environment. + * + * @author Stephane Nicoll + */ +@ConditionalOnClass(PlatformTransactionManager.class) +@ConditionalOnMissingBean(BatchConfigurer.class) +@Configuration +class BatchConfigurerConfiguration { + + @ConditionalOnMissingBean(name = "entityManagerFactory") + static class JdbcBatchConfiguration { + + @Bean + public BasicBatchConfigurer batchConfigurer(BatchProperties properties, + DataSource dataSource, + ObjectProvider transactionManagerCustomizers) { + return new BasicBatchConfigurer(properties, dataSource, + transactionManagerCustomizers.getIfAvailable()); + } + + } + + @ConditionalOnClass(name = "javax.persistence.EntityManagerFactory") + @ConditionalOnBean(name = "entityManagerFactory") + static class JpaBatchConfiguration { + + @Bean + public JpaBatchConfigurer batchConfigurer(BatchProperties properties, + DataSource dataSource, + ObjectProvider transactionManagerCustomizers, + EntityManagerFactory entityManagerFactory) { + return new JpaBatchConfigurer(properties, dataSource, + transactionManagerCustomizers.getIfAvailable(), entityManagerFactory); + } + + } + +} + diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JpaBatchConfigurer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JpaBatchConfigurer.java new file mode 100644 index 0000000000..a75251d8ce --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JpaBatchConfigurer.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.batch; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * A {@link BasicBatchConfigurer} tailored for JPA. + * + * @author Stephane Nicoll + */ +public class JpaBatchConfigurer extends BasicBatchConfigurer { + + private static final Log logger = LogFactory.getLog(JpaBatchConfigurer.class); + + private final EntityManagerFactory entityManagerFactory; + + /** + * Create a new {@link BasicBatchConfigurer} instance. + * @param properties the batch properties + * @param dataSource the underlying data source + * @param entityManagerFactory the entity manager factory (or {@code null}) + * @param transactionManagerCustomizers transaction manager customizers (or + * {@code null}) + */ + protected JpaBatchConfigurer(BatchProperties properties, DataSource dataSource, + TransactionManagerCustomizers transactionManagerCustomizers, + EntityManagerFactory entityManagerFactory) { + super(properties, dataSource, transactionManagerCustomizers); + this.entityManagerFactory = entityManagerFactory; + } + + @Override + protected String determineIsolationLevel() { + logger.warn( + "JPA does not support custom isolation levels, so locks may not be taken when launching Jobs"); + return "ISOLATION_DEFAULT"; + } + + @Override + protected PlatformTransactionManager createTransactionManager() { + return new JpaTransactionManager(this.entityManagerFactory); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationWithoutJpaTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationWithoutJpaTests.java new file mode 100644 index 0000000000..da1d9bb490 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationWithoutJpaTests.java @@ -0,0 +1,119 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.batch; + +import javax.sql.DataSource; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.test.City; +import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.boot.junit.runner.classpath.ClassPathExclusions; +import org.springframework.boot.junit.runner.classpath.ModifiedClassPathRunner; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.PlatformTransactionManager; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link BatchAutoConfiguration} when JPA is not on the classpath. + * + * @author Stephane Nicoll + */ +@RunWith(ModifiedClassPathRunner.class) +@ClassPathExclusions("hibernate-jpa-*.jar") +public class BatchAutoConfigurationWithoutJpaTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void jdbcWithDefaultSettings() throws Exception { + load(new Class[] { DefaultConfiguration.class, + EmbeddedDataSourceConfiguration.class }, + "spring.datasource.generate-unique-name=true"); + assertThat(this.context.getBeansOfType(JobLauncher.class)).hasSize(1); + assertThat(this.context.getBeansOfType(JobExplorer.class)).hasSize(1); + assertThat(this.context.getBeansOfType(JobRepository.class)).hasSize(1); + assertThat(this.context.getBeansOfType(PlatformTransactionManager.class)) + .hasSize(1); + assertThat(this.context.getBean(PlatformTransactionManager.class).toString()) + .contains("DataSourceTransactionManager"); + assertThat( + this.context.getBean(BatchProperties.class).getInitializer().isEnabled()) + .isTrue(); + assertThat(new JdbcTemplate(this.context.getBean(DataSource.class)) + .queryForList("select * from BATCH_JOB_EXECUTION")).isEmpty(); + assertThat(this.context.getBean(JobExplorer.class) + .findRunningJobExecutions("test")).isEmpty(); + assertThat(this.context.getBean(JobRepository.class) + .getLastJobExecution("test", new JobParameters())).isNull(); + } + + @Test + public void jdbcWithCustomPrefix() throws Exception { + load(new Class[] { DefaultConfiguration.class, + EmbeddedDataSourceConfiguration.class }, + "spring.datasource.generate-unique-name=true", + "spring.batch.schema:classpath:batch/custom-schema-hsql.sql", + "spring.batch.tablePrefix:PREFIX_"); + assertThat(new JdbcTemplate(this.context.getBean(DataSource.class)) + .queryForList("select * from PREFIX_JOB_EXECUTION")).isEmpty(); + assertThat(this.context.getBean(JobExplorer.class) + .findRunningJobExecutions("test")).isEmpty(); + assertThat(this.context.getBean(JobRepository.class) + .getLastJobExecution("test", new JobParameters())).isNull(); + + } + + private void load(Class[] configs, String... environment) { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(ctx, environment); + if (configs != null) { + ctx.register(configs); + } + ctx.register(BatchAutoConfiguration.class, TransactionAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + ctx.refresh(); + this.context = ctx; + } + + @EnableBatchProcessing + @TestAutoConfigurationPackage(City.class) + protected static class DefaultConfiguration { + + } + +}