From 39a94428d32cc613966ca394ffdb442ef1fb49c2 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Mon, 9 Jun 2014 09:36:00 +0100 Subject: [PATCH] Add @Conditionals to permit JPA/Mongo mixed usage I decided to go with both approaches (make the autoconfig for repositories @ConditionalOnMissingBean(RepositoryFactoryBeanSupport), so the first one wins; and also make them conditional on spring.data.*.repositories.enabled=true. The ordering problem is still there really (it's not defined which repositories will be created by the autoconfig), so if a user is going to have 2 repository implementations on the classpath, he is going to have to either choose one to disable, or manualy @Enable* the other one. Fixes gh-1042 --- ...icsearchRepositoriesAutoConfiguration.java | 6 +- .../jpa/JpaRepositoriesAutoConfiguration.java | 6 +- .../MongoRepositoriesAutoConfiguration.java | 6 +- .../SolrRepositoriesAutoConfiguration.java | 6 +- .../data/jpa/city/CityRepository.java | 4 +- ...ngoRepositoriesAutoConfigurationTests.java | 77 +++++++++++++++++-- spring-boot-docs/src/main/asciidoc/howto.adoc | 21 ++++- 7 files changed, 110 insertions(+), 16 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfiguration.java index 389a7efcd3..4d2163ed2f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfiguration.java @@ -19,12 +19,13 @@ package org.springframework.boot.autoconfigure.data.elasticsearch; import org.elasticsearch.client.Client; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; -import org.springframework.data.elasticsearch.repository.support.ElasticsearchRepositoryFactoryBean; +import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; /** * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Elasticsearch @@ -37,7 +38,8 @@ import org.springframework.data.elasticsearch.repository.support.ElasticsearchRe */ @Configuration @ConditionalOnClass({ Client.class, ElasticsearchRepository.class }) -@ConditionalOnMissingBean(ElasticsearchRepositoryFactoryBean.class) +@ConditionalOnExpression("${spring.data.elasticsearch.repositories.enabled:true}") +@ConditionalOnMissingBean(RepositoryFactoryBeanSupport.class) @Import(ElasticsearchRepositoriesAutoConfigureRegistrar.class) public class ElasticsearchRepositoriesAutoConfiguration { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java index 671c6ececa..d6565b4771 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java @@ -22,6 +22,7 @@ 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.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; @@ -29,7 +30,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean; +import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.web.PageableHandlerMethodArgumentResolver; import org.springframework.data.web.config.EnableSpringDataWebSupport; @@ -55,7 +56,8 @@ import org.springframework.data.web.config.EnableSpringDataWebSupport; @Configuration @ConditionalOnBean(DataSource.class) @ConditionalOnClass(JpaRepository.class) -@ConditionalOnMissingBean(JpaRepositoryFactoryBean.class) +@ConditionalOnMissingBean(RepositoryFactoryBeanSupport.class) +@ConditionalOnExpression("${spring.data.jpa.repositories.enabled:true}") @Import(JpaRepositoriesAutoConfigureRegistrar.class) @AutoConfigureAfter(HibernateJpaAutoConfiguration.class) public class JpaRepositoriesAutoConfiguration { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoRepositoriesAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoRepositoriesAutoConfiguration.java index 0d0f04c5ef..068a4e4d77 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoRepositoriesAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoRepositoriesAutoConfiguration.java @@ -19,13 +19,14 @@ package org.springframework.boot.autoconfigure.data.mongo; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 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.mongo.MongoAutoConfiguration; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; -import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean; +import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import com.mongodb.Mongo; @@ -52,7 +53,8 @@ import com.mongodb.Mongo; */ @Configuration @ConditionalOnClass({ Mongo.class, MongoRepository.class }) -@ConditionalOnMissingBean(MongoRepositoryFactoryBean.class) +@ConditionalOnMissingBean(RepositoryFactoryBeanSupport.class) +@ConditionalOnExpression("${spring.data.mongo.repositories.enabled:true}") @Import(MongoRepositoriesAutoConfigureRegistrar.class) @AutoConfigureAfter(MongoAutoConfiguration.class) public class MongoRepositoriesAutoConfiguration { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/solr/SolrRepositoriesAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/solr/SolrRepositoriesAutoConfiguration.java index b614d99b40..96211b45d9 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/solr/SolrRepositoriesAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/solr/SolrRepositoriesAutoConfiguration.java @@ -18,11 +18,12 @@ package org.springframework.boot.autoconfigure.data.solr; import org.apache.solr.client.solrj.SolrServer; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.solr.repository.SolrRepository; -import org.springframework.data.solr.repository.support.SolrRepositoryFactoryBean; /** * Enables auto configuration for Spring Data Solr repositories. @@ -42,7 +43,8 @@ import org.springframework.data.solr.repository.support.SolrRepositoryFactoryBea */ @Configuration @ConditionalOnClass({ SolrServer.class, SolrRepository.class }) -@ConditionalOnMissingBean(SolrRepositoryFactoryBean.class) +@ConditionalOnMissingBean(RepositoryFactoryBeanSupport.class) +@ConditionalOnExpression("${spring.data.solr.repositories.enabled:true}") @Import(SolrRepositoriesAutoConfigureRegistrar.class) public class SolrRepositoriesAutoConfiguration { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/city/CityRepository.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/city/CityRepository.java index 2ca8845171..d222a5b73d 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/city/CityRepository.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/city/CityRepository.java @@ -18,9 +18,9 @@ package org.springframework.boot.autoconfigure.data.jpa.city; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.repository.Repository; +import org.springframework.data.jpa.repository.JpaRepository; -public interface CityRepository extends Repository { +public interface CityRepository extends JpaRepository { Page findAll(Pageable pageable); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MixedMongoRepositoriesAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MixedMongoRepositoriesAutoConfigurationTests.java index d34e05e723..0221d9650d 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MixedMongoRepositoriesAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MixedMongoRepositoriesAutoConfigurationTests.java @@ -16,12 +16,16 @@ package org.springframework.boot.autoconfigure.data.mongo; +import java.util.ArrayList; +import java.util.List; + import org.junit.After; import org.junit.Test; -import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; +import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration; import org.springframework.boot.autoconfigure.data.jpa.city.City; import org.springframework.boot.autoconfigure.data.jpa.city.CityRepository; +import org.springframework.boot.autoconfigure.data.mongo.MixedMongoRepositoriesAutoConfigurationTests.BaseConfiguration.Registrar; import org.springframework.boot.autoconfigure.data.mongo.country.Country; import org.springframework.boot.autoconfigure.data.mongo.country.CountryRepository; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; @@ -34,6 +38,8 @@ import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; @@ -75,13 +81,57 @@ public class MixedMongoRepositoriesAutoConfigurationTests { assertNotNull(this.context.getBean(CityRepository.class)); } + @Test + public void testJpaRepositoryConfigurationWithMongoTemplate() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.initialize:false"); + this.context.register(JpaConfiguration.class, BaseConfiguration.class); + this.context.refresh(); + assertNotNull(this.context.getBean(CityRepository.class)); + } + + @Test + public void testJpaRepositoryConfigurationWithMongoOverlap() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.initialize:false"); + this.context.register(OverlapConfiguration.class, BaseConfiguration.class); + this.context.refresh(); + assertNotNull(this.context.getBean(CityRepository.class)); + } + + @Test + public void testJpaRepositoryConfigurationWithMongoOverlapDisabled() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.initialize:false", + "spring.data.mongo.repositories.enabled:false"); + this.context.register(OverlapConfiguration.class, BaseConfiguration.class); + this.context.refresh(); + assertNotNull(this.context.getBean(CityRepository.class)); + } + @Configuration - @Import({ MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, - MongoRepositoriesAutoConfiguration.class, DataSourceAutoConfiguration.class, - HibernateJpaAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class }) + @Import(Registrar.class) protected static class BaseConfiguration { + protected static class Registrar implements ImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + List names = new ArrayList(); + for (Class type : new Class[] { DataSourceAutoConfiguration.class, + HibernateJpaAutoConfiguration.class, + JpaRepositoriesAutoConfiguration.class, + MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, + MongoRepositoriesAutoConfiguration.class }) { + names.add(type.getName()); + } + return names.toArray(new String[0]); + } + } + } @Configuration @@ -100,4 +150,21 @@ public class MixedMongoRepositoriesAutoConfigurationTests { protected static class MixedConfiguration { } + + @Configuration + @TestAutoConfigurationPackage(MongoAutoConfigurationTests.class) + @EntityScan(basePackageClasses = City.class) + @EnableJpaRepositories(basePackageClasses = CityRepository.class) + protected static class JpaConfiguration { + + } + + // In this one the Jpa repositories and the autoconfiguration packages overlap, so + // Mongo will try and configure the same repositories + @Configuration + @TestAutoConfigurationPackage(CityRepository.class) + @EnableJpaRepositories(basePackageClasses = CityRepository.class) + protected static class OverlapConfiguration { + + } } diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index fc30d498bc..a9c90ce8a4 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -1004,7 +1004,6 @@ Spring Boot tries to guess the location of your `@Repository` definitions, based annotation (from Spring Data JPA). - [[howto-separate-entity-definitions-from-spring-configuration]] === Separate @Entity definitions from Spring configuration Spring Boot tries to guess the location of your `@Entity` definitions, based on the @@ -1116,6 +1115,26 @@ See https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java[`JpaBaseConfiguration`] for the default settings. +[[howto-use-spring-data-jpa--and-mongo-repositories]] +=== Use Spring Data JPA and Mongo repositories + +Spring Data JPA and Spring Data Mongo can both create `Repository` +implementations for you automatically. If they are both present on the +classpath, you might have to do some extra configuration to tell +Spring Boot which one (or both) you want to create repositories for +you. The most explicit way to do that is to use the standard Spring +Data `@Enable*Repositories` and tell it the location of your +`Repository` interfaces (where "*" is "Jpa" or "Mongo" or both). + +There are also flags `spring.data.*.repositories.enabled` that you can +use to switch the autoconfigured repositories on and off in external +configuration. This is useful for instance in case you want to switch +off the Mongo repositories and still use the autoconfigured +`MongoTemplate`. + +The same obstacle and the same features exist for other autoconfigured +Spring Data repository types (Elasticsearch, Solr). Just change the +names of the annotations and flags respectively. [[howto-database-initialization]]