Split JPA-specific bits of Batch auto-config

This commit separates the JPA-specific bits of the batch
auto-configuration so that it is possible to reuse most of the logic if
JPA isn't available at all.

Closes gh-8877
pull/8889/head
Stephane Nicoll 8 years ago
parent c4cba6b0ea
commit bcb13bea63

@ -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);
}
}

@ -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> transactionManagerCustomizers) {
return new BasicBatchConfigurer(this.properties, dataSource,
entityManagerFactory, transactionManagerCustomizers.getIfAvailable());
}
@Bean
@ConditionalOnMissingBean(name = "entityManagerFactory")
public BasicBatchConfigurer basicBatchConfigurer(DataSource dataSource,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
return new BasicBatchConfigurer(this.properties, dataSource,
transactionManagerCustomizers.getIfAvailable());
}
}
}

@ -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> 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> transactionManagerCustomizers,
EntityManagerFactory entityManagerFactory) {
return new JpaBatchConfigurer(properties, dataSource,
transactionManagerCustomizers.getIfAvailable(), entityManagerFactory);
}
}
}

@ -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);
}
}

@ -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 {
}
}
Loading…
Cancel
Save