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 new file mode 100644 index 0000000000..218a673484 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BasicBatchConfigurer.java @@ -0,0 +1,99 @@ +/* + * Copyright 2012-2013 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.annotation.PostConstruct; +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +import org.springframework.batch.core.configuration.annotation.BatchConfigurer; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.support.SimpleJobLauncher; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.stereotype.Component; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * @author Dave Syer + */ +@Component +public class BasicBatchConfigurer implements BatchConfigurer { + + private DataSource dataSource; + private EntityManagerFactory entityManagerFactory; + private PlatformTransactionManager transactionManager; + private JobRepository jobRepository; + private JobLauncher jobLauncher; + + public BasicBatchConfigurer(DataSource dataSource) { + this(dataSource, null); + } + + public BasicBatchConfigurer(DataSource dataSource, + EntityManagerFactory entityManagerFactory) { + this.entityManagerFactory = entityManagerFactory; + this.dataSource = dataSource; + } + + @Override + public JobRepository getJobRepository() { + return this.jobRepository; + } + + @Override + public PlatformTransactionManager getTransactionManager() { + return this.transactionManager; + } + + @Override + public JobLauncher getJobLauncher() { + return this.jobLauncher; + } + + @PostConstruct + public void initialize() throws Exception { + this.transactionManager = createTransactionManager(); + this.jobRepository = createJobRepository(); + this.jobLauncher = createJobLauncher(); + } + + private JobLauncher createJobLauncher() throws Exception { + SimpleJobLauncher jobLauncher = new SimpleJobLauncher(); + jobLauncher.setJobRepository(getJobRepository()); + jobLauncher.afterPropertiesSet(); + return jobLauncher; + } + + protected JobRepository createJobRepository() throws Exception { + JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + factory.setDataSource(this.dataSource); + factory.setTransactionManager(getTransactionManager()); + factory.afterPropertiesSet(); + return (JobRepository) factory.getObject(); + } + + protected PlatformTransactionManager createTransactionManager() { + 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 4cddf95839..83c65f31dd 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,9 +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; @@ -29,11 +31,13 @@ import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ExitCodeGenerator; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcOperations; @@ -49,6 +53,7 @@ import org.springframework.util.StringUtils; */ @Configuration @ConditionalOnClass({ JobLauncher.class, DataSource.class, JdbcOperations.class }) +@AutoConfigureAfter(HibernateJpaAutoConfiguration.class) public class BatchAutoConfiguration { @Value("${spring.batch.job.name:}") @@ -106,4 +111,27 @@ public class BatchAutoConfiguration { return factory; } + @ConditionalOnClass(name = "javax.persistence.EntityManagerFactory") + @ConditionalOnMissingBean(BatchConfigurer.class) + @Configuration + protected static class JpaBatchConfiguration { + + // 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 BatchConfigurer jpaBatchConfigurer(DataSource dataSource, + EntityManagerFactory entityManagerFactory) { + return new BasicBatchConfigurer(dataSource, entityManagerFactory); + } + + @Bean + @ConditionalOnMissingBean(name = "entityManagerFactory") + public BatchConfigurer basicBatchConfigurer(DataSource dataSource) { + return new BasicBatchConfigurer(dataSource); + } + + } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/JpaRepositoriesAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/JpaRepositoriesAutoConfiguration.java index 62d4a71e41..6691d835dc 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/JpaRepositoriesAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/JpaRepositoriesAutoConfiguration.java @@ -24,7 +24,7 @@ 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.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.jpa.repository.JpaRepository; @@ -44,7 +44,7 @@ import org.springframework.data.web.config.EnableSpringDataWebSupport; @ConditionalOnClass(JpaRepository.class) @ConditionalOnMissingBean(JpaRepositoryFactoryBean.class) @Import(JpaRepositoriesAutoConfigureRegistrar.class) -@AutoConfigureAfter(DataSourceAutoConfiguration.class) +@AutoConfigureAfter(HibernateJpaAutoConfiguration.class) public class JpaRepositoriesAutoConfiguration { @Configuration diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java index 5124a97b9c..9e10bc1d1b 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java @@ -24,9 +24,11 @@ import javax.sql.DataSource; import org.hibernate.cfg.ImprovedNamingStrategy; import org.hibernate.ejb.HibernateEntityManager; import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.context.annotation.Configuration; @@ -47,6 +49,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; HibernateEntityManager.class }) @ConditionalOnBean(DataSource.class) @EnableTransactionManagement +@AutoConfigureAfter(DataSourceAutoConfiguration.class) public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration implements BeanClassLoaderAware { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java index d16d731a15..960bb5b0e4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java @@ -34,7 +34,6 @@ import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; -import org.springframework.orm.jpa.EntityManagerFactoryInfo; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; @@ -72,7 +71,7 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware, Environm } @Bean - @ConditionalOnMissingBean(EntityManagerFactoryInfo.class) + @ConditionalOnMissingBean(name = "entityManagerFactory") public LocalContainerEntityManagerFactoryBean entityManagerFactory( JpaVendorAdapter jpaVendorAdapter) { LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java index 7682694b12..c8560b77ac 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.batch; import java.util.Collection; import java.util.Collections; +import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import org.junit.After; @@ -39,15 +40,21 @@ import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.TestUtils; +import org.springframework.boot.autoconfigure.ComponentScanDetectorConfiguration; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.test.City; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.PlatformTransactionManager; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; /** * Tests for {@link BatchAutoConfiguration}. @@ -121,7 +128,25 @@ public class BatchAutoConfigurationTests { .queryForList("select * from BATCH_JOB_EXECUTION"); } + @Test + public void testUsingJpa() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + // The order is very important here: DataSource -> Hibernate -> Batch + this.context.register(TestConfiguration.class, + EmbeddedDataSourceConfiguration.class, + ComponentScanDetectorConfiguration.class, + HibernateJpaAutoConfiguration.class, BatchAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + PlatformTransactionManager transactionManager = this.context + .getBean(PlatformTransactionManager.class); + // It's a lazy proxy, but it does render its target if you ask for toString(): + assertTrue(transactionManager.toString().contains("JpaTransactionManager")); + assertNotNull(this.context.getBean(EntityManagerFactory.class)); + } + @EnableBatchProcessing + @ComponentScan(basePackageClasses = City.class) protected static class TestConfiguration { } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java index 90b3b5031a..225af0b7d1 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java @@ -76,8 +76,8 @@ public abstract class AbstractJpaAutoConfigurationTests { @Test public void testDataSourceTransactionManagerNotCreated() throws Exception { - setupTestConfiguration(); this.context.register(DataSourceTransactionManagerAutoConfiguration.class); + setupTestConfiguration(); this.context.refresh(); assertNotNull(this.context.getBean(DataSource.class)); assertTrue(this.context.getBean("transactionManager") instanceof JpaTransactionManager); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/test/City.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/test/City.java index 4bec85b99e..1f808274f8 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/test/City.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/test/City.java @@ -47,10 +47,12 @@ public class City implements Serializable { protected City() { } - public City(String name, String country) { + public City(String name, String state, String country, String map) { super(); this.name = name; + this.state = state; this.country = country; + this.map = map; } public String getName() {