Ignore non-default candidates in type-based matching
Closes gh-22403
This commit is contained in:
@@ -25,6 +25,7 @@ import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
|
||||
@@ -69,30 +70,37 @@ public @interface ConditionalOnBean {
|
||||
/**
|
||||
* The class types of beans that should be checked. The condition matches when beans
|
||||
* of all classes specified are contained in the {@link BeanFactory}. Beans that are
|
||||
* not autowire candidates are ignored.
|
||||
* not autowire candidates or that are not default candidates are ignored.
|
||||
* @return the class types of beans to check
|
||||
* @see Bean#autowireCandidate()
|
||||
* @see BeanDefinition#isAutowireCandidate
|
||||
* @see Bean#defaultCandidate()
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate
|
||||
*/
|
||||
Class<?>[] value() default {};
|
||||
|
||||
/**
|
||||
* The class type names of beans that should be checked. The condition matches when
|
||||
* beans of all classes specified are contained in the {@link BeanFactory}. Beans that
|
||||
* are not autowire candidates are ignored.
|
||||
* are not autowire candidates or that are not default candidates are ignored.
|
||||
* @return the class type names of beans to check
|
||||
* @see Bean#autowireCandidate()
|
||||
* @see BeanDefinition#isAutowireCandidate
|
||||
* @see Bean#defaultCandidate()
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate
|
||||
*/
|
||||
String[] type() default {};
|
||||
|
||||
/**
|
||||
* The annotation type decorating a bean that should be checked. The condition matches
|
||||
* when all the annotations specified are defined on beans in the {@link BeanFactory}.
|
||||
* Beans that are not autowire candidates are ignored.
|
||||
* Beans that are not autowire candidates or that are not default candidates are
|
||||
* ignored.
|
||||
* @return the class-level annotation types to check
|
||||
* @see Bean#autowireCandidate()
|
||||
* @see BeanDefinition#isAutowireCandidate
|
||||
* @see Bean#defaultCandidate()
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate
|
||||
*/
|
||||
Class<? extends Annotation>[] annotation() default {};
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
|
||||
@@ -70,20 +71,24 @@ public @interface ConditionalOnMissingBean {
|
||||
/**
|
||||
* The class types of beans that should be checked. The condition matches when no bean
|
||||
* of each class specified is contained in the {@link BeanFactory}. Beans that are not
|
||||
* autowire candidates are ignored.
|
||||
* autowire candidates or that are not default candidates are ignored.
|
||||
* @return the class types of beans to check
|
||||
* @see Bean#autowireCandidate()
|
||||
* @see BeanDefinition#isAutowireCandidate
|
||||
* @see Bean#defaultCandidate()
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate
|
||||
*/
|
||||
Class<?>[] value() default {};
|
||||
|
||||
/**
|
||||
* The class type names of beans that should be checked. The condition matches when no
|
||||
* bean of each class specified is contained in the {@link BeanFactory}. Beans that
|
||||
* are not autowire candidates are ignored.
|
||||
* are not autowire candidates or that are not default candidates are ignored.
|
||||
* @return the class type names of beans to check
|
||||
* @see Bean#autowireCandidate()
|
||||
* @see BeanDefinition#isAutowireCandidate
|
||||
* @see Bean#defaultCandidate()
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate
|
||||
*/
|
||||
String[] type() default {};
|
||||
|
||||
@@ -105,10 +110,13 @@ public @interface ConditionalOnMissingBean {
|
||||
/**
|
||||
* The annotation type decorating a bean that should be checked. The condition matches
|
||||
* when each annotation specified is missing from all beans in the
|
||||
* {@link BeanFactory}. Beans that are not autowire candidates are ignored.
|
||||
* {@link BeanFactory}. Beans that are not autowire candidates or that are not default
|
||||
* candidates are ignored.
|
||||
* @return the class-level annotation types to check
|
||||
* @see Bean#autowireCandidate()
|
||||
* @see BeanDefinition#isAutowireCandidate
|
||||
* @see Bean#defaultCandidate()
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate
|
||||
*/
|
||||
Class<? extends Annotation>[] annotation() default {};
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
|
||||
@@ -53,14 +54,16 @@ public @interface ConditionalOnSingleCandidate {
|
||||
/**
|
||||
* The class type of bean that should be checked. The condition matches if a bean of
|
||||
* the class specified is contained in the {@link BeanFactory} and a primary candidate
|
||||
* exists in case of multiple instances. Beans that are not autowire candidates are
|
||||
* ignored.
|
||||
* exists in case of multiple instances. Beans that are not autowire candidates or
|
||||
* that are not default candidates are ignored.
|
||||
* <p>
|
||||
* This attribute may <strong>not</strong> be used in conjunction with
|
||||
* {@link #type()}, but it may be used instead of {@link #type()}.
|
||||
* @return the class type of the bean to check
|
||||
* @see Bean#autowireCandidate()
|
||||
* @see BeanDefinition#isAutowireCandidate
|
||||
* @see Bean#defaultCandidate()
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate
|
||||
*/
|
||||
Class<?> value() default Object.class;
|
||||
|
||||
@@ -68,13 +71,15 @@ public @interface ConditionalOnSingleCandidate {
|
||||
* The class type name of bean that should be checked. The condition matches if a bean
|
||||
* of the class specified is contained in the {@link BeanFactory} and a primary
|
||||
* candidate exists in case of multiple instances. Beans that are not autowire
|
||||
* candidates are ignored.
|
||||
* candidates or that are not default candidates are ignored.
|
||||
* <p>
|
||||
* This attribute may <strong>not</strong> be used in conjunction with
|
||||
* {@link #value()}, but it may be used instead of {@link #value()}.
|
||||
* @return the class type name of the bean to check
|
||||
* @see Bean#autowireCandidate()
|
||||
* @see BeanDefinition#isAutowireCandidate
|
||||
* @see Bean#defaultCandidate()
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate
|
||||
*/
|
||||
String type() default "";
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.SingletonBeanRegistry;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -217,8 +218,8 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
|
||||
Map<String, BeanDefinition> typeMatchedDefinitions = getBeanDefinitionsForType(classLoader,
|
||||
considerHierarchy, beanFactory, type, parameterizedContainers);
|
||||
Set<String> typeMatchedNames = matchedNamesFrom(typeMatchedDefinitions,
|
||||
(name, definition) -> !beansIgnoredByType.contains(name) && !ScopedProxyUtils.isScopedTarget(name)
|
||||
&& (definition == null || definition.isAutowireCandidate()));
|
||||
(name, definition) -> isCandidate(name, definition, beansIgnoredByType)
|
||||
&& !ScopedProxyUtils.isScopedTarget(name));
|
||||
if (typeMatchedNames.isEmpty()) {
|
||||
result.recordUnmatchedType(type);
|
||||
}
|
||||
@@ -230,8 +231,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
|
||||
Map<String, BeanDefinition> annotationMatchedDefinitions = getBeanDefinitionsForAnnotation(classLoader,
|
||||
beanFactory, annotation, considerHierarchy);
|
||||
Set<String> annotationMatchedNames = matchedNamesFrom(annotationMatchedDefinitions,
|
||||
(name, definition) -> !beansIgnoredByType.contains(name)
|
||||
&& (definition == null || definition.isAutowireCandidate()));
|
||||
(name, definition) -> isCandidate(name, definition, beansIgnoredByType));
|
||||
if (annotationMatchedNames.isEmpty()) {
|
||||
result.recordUnmatchedAnnotation(annotation);
|
||||
}
|
||||
@@ -262,6 +262,18 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
|
||||
return matchedNames;
|
||||
}
|
||||
|
||||
private boolean isCandidate(String name, BeanDefinition definition, Set<String> ignoredBeans) {
|
||||
return (!ignoredBeans.contains(name))
|
||||
&& (definition == null || (definition.isAutowireCandidate() && isDefaultCandidate(definition)));
|
||||
}
|
||||
|
||||
private boolean isDefaultCandidate(BeanDefinition definition) {
|
||||
if (definition instanceof AbstractBeanDefinition abstractBeanDefinition) {
|
||||
return abstractBeanDefinition.isDefaultCandidate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Set<String> getNamesOfBeansIgnoredByType(ClassLoader classLoader, ListableBeanFactory beanFactory,
|
||||
boolean considerHierarchy, Set<String> ignoredTypes, Set<Class<?>> parameterizedContainers) {
|
||||
Set<String> result = null;
|
||||
|
||||
@@ -261,6 +261,25 @@ class ConditionalOnBeanTests {
|
||||
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void conditionalOnBeanTypeIgnoresNotDefaultCandidateBean() {
|
||||
this.contextRunner.withUserConfiguration(NotDefaultCandidateConfiguration.class, OnBeanClassConfiguration.class)
|
||||
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void conditionalOnBeanNameMatchesNotDefaultCandidateBean() {
|
||||
this.contextRunner.withUserConfiguration(NotDefaultCandidateConfiguration.class, OnBeanNameConfiguration.class)
|
||||
.run((context) -> assertThat(context).hasBean("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void conditionalOnAnnotatedBeanIgnoresNotDefaultCandidateBean() {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(AnnotatedNotDefaultCandidateConfig.class, OnAnnotationConfiguration.class)
|
||||
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
|
||||
}
|
||||
|
||||
private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) {
|
||||
return (context) -> {
|
||||
String[] beans = context.getBeanNamesForType(ExampleBean.class);
|
||||
@@ -356,6 +375,16 @@ class ConditionalOnBeanTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class NotDefaultCandidateConfiguration {
|
||||
|
||||
@Bean(defaultCandidate = false)
|
||||
String foo() {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ImportResource("org/springframework/boot/autoconfigure/condition/foo.xml")
|
||||
static class XmlConfiguration {
|
||||
@@ -569,6 +598,16 @@ class ConditionalOnBeanTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class AnnotatedNotDefaultCandidateConfig {
|
||||
|
||||
@Bean(defaultCandidate = false)
|
||||
ExampleBean exampleBean() {
|
||||
return new ExampleBean("value");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TestAnnotation
|
||||
static class ExampleBean {
|
||||
|
||||
|
||||
@@ -363,6 +363,25 @@ class ConditionalOnMissingBeanTests {
|
||||
.run((context) -> assertThat(context).hasBean("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeBasedMatchingIgnoresBeanThatIsNotDefaultCandidate() {
|
||||
this.contextRunner.withUserConfiguration(NotDefaultCandidateConfig.class, OnBeanTypeConfiguration.class)
|
||||
.run((context) -> assertThat(context).hasBean("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nameBasedMatchingConsidersBeanThatIsNotDefaultCandidate() {
|
||||
this.contextRunner.withUserConfiguration(NotDefaultCandidateConfig.class, OnBeanNameConfiguration.class)
|
||||
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationBasedMatchingIgnoresBeanThatIsNotDefaultCandidateBean() {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(AnnotatedNotDefaultCandidateConfig.class, OnAnnotationConfiguration.class)
|
||||
.run((context) -> assertThat(context).hasBean("bar"));
|
||||
}
|
||||
|
||||
private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) {
|
||||
return (context) -> {
|
||||
String[] beans = context.getBeanNamesForType(ExampleBean.class);
|
||||
@@ -607,6 +626,16 @@ class ConditionalOnMissingBeanTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class NotDefaultCandidateConfig {
|
||||
|
||||
@Bean(defaultCandidate = false)
|
||||
String foo() {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingBean(name = "foo")
|
||||
static class HierarchyConsidered {
|
||||
@@ -780,6 +809,16 @@ class ConditionalOnMissingBeanTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class AnnotatedNotDefaultCandidateConfig {
|
||||
|
||||
@Bean(autowireCandidate = false)
|
||||
ExampleBean exampleBean() {
|
||||
return new ExampleBean("value");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TestAnnotation
|
||||
static class ExampleBean {
|
||||
|
||||
|
||||
@@ -184,6 +184,17 @@ class ConditionalOnSingleCandidateTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleCandidateMultipleCandidatesOneDefaultCandidate() {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(AlphaConfiguration.class, BravoNonDefaultConfiguration.class,
|
||||
OnBeanSingleCandidateConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasBean("consumer");
|
||||
assertThat(context.getBean("consumer")).isEqualTo("alpha");
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnSingleCandidate(String.class)
|
||||
static class OnBeanSingleCandidateConfiguration {
|
||||
@@ -303,4 +314,14 @@ class ConditionalOnSingleCandidateTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class BravoNonDefaultConfiguration {
|
||||
|
||||
@Bean(defaultCandidate = false)
|
||||
String bravo() {
|
||||
return "bravo";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -110,43 +110,41 @@ See xref:reference:data/sql.adoc#data.sql.datasource[] in the "`Spring Boot Feat
|
||||
[[howto.data-access.configure-two-datasources]]
|
||||
== Configure Two DataSources
|
||||
|
||||
If you need to configure multiple data sources, you can apply the same tricks that were described in the previous section.
|
||||
You must, however, mark one of the `DataSource` instances as `@Primary`, because various auto-configurations down the road expect to be able to get one by type.
|
||||
To define an additional `DataSource`, an approach that's similar to the previous section can be used.
|
||||
A key difference is that the `DataSource` `@Bean` must be declared with `defaultCandidate=false`.
|
||||
This prevents the auto-configured `DataSource` from backing off.
|
||||
To allow the additional `DataSource` to be injected where it's needed, also annotate it with `@Qualifier` as shown in the following example:
|
||||
|
||||
If you create your own `DataSource`, the auto-configuration backs off.
|
||||
In the following example, we provide the _exact_ same feature set as the auto-configuration provides on the primary data source:
|
||||
include-code::MyAdditionalDataSourceConfiguration[]
|
||||
|
||||
include-code::MyDataSourcesConfiguration[]
|
||||
To consume the additional `DataSource`, annotate the injection point with the same `@Qualifier`.
|
||||
|
||||
TIP: `firstDataSourceProperties` has to be flagged as `@Primary` so that the database initializer feature uses your copy (if you use the initializer).
|
||||
|
||||
Both data sources are also bound for advanced customizations.
|
||||
For instance, you could configure them as follows:
|
||||
The auto-configured and additional data sources can be configured as follows:
|
||||
|
||||
[configprops%novalidate,yaml]
|
||||
----
|
||||
spring:
|
||||
datasource:
|
||||
url: "jdbc:mysql://localhost/first"
|
||||
username: "dbuser"
|
||||
password: "dbpass"
|
||||
configuration:
|
||||
maximum-pool-size: 30
|
||||
app:
|
||||
datasource:
|
||||
first:
|
||||
url: "jdbc:mysql://localhost/first"
|
||||
username: "dbuser"
|
||||
password: "dbpass"
|
||||
configuration:
|
||||
maximum-pool-size: 30
|
||||
|
||||
second:
|
||||
url: "jdbc:mysql://localhost/second"
|
||||
username: "dbuser"
|
||||
password: "dbpass"
|
||||
max-total: 30
|
||||
url: "jdbc:mysql://localhost/second"
|
||||
username: "dbuser"
|
||||
password: "dbpass"
|
||||
max-total: 30
|
||||
----
|
||||
|
||||
You can apply the same concept to the secondary `DataSource` as well, as shown in the following example:
|
||||
More advanced, implementation-specific, configuration of the auto-configured `DataSource` is available through the `spring.datasource.configuration.*` properties.
|
||||
You can apply the same concept to the additional `DataSource` as well, as shown in the following example:
|
||||
|
||||
include-code::MyCompleteDataSourcesConfiguration[]
|
||||
include-code::MyCompleteAdditionalDataSourceConfiguration[]
|
||||
|
||||
The preceding example configures two data sources on custom namespaces with the same logic as Spring Boot would use in auto-configuration.
|
||||
Note that each `configuration` sub namespace provides advanced settings based on the chosen implementation.
|
||||
The preceding example configures the additional data source with the same logic as Spring Boot would use in auto-configuration.
|
||||
Note that the `app.datasource.configuration.*` properties provide advanced settings based on the chosen implementation.
|
||||
|
||||
|
||||
|
||||
@@ -295,26 +293,28 @@ You can disable or tune this behavior by registering a `HibernatePropertiesCusto
|
||||
To take full control of the configuration of the `EntityManagerFactory`, you need to add a `@Bean` named '`entityManagerFactory`'.
|
||||
Spring Boot auto-configuration switches off its entity manager in the presence of a bean of that type.
|
||||
|
||||
NOTE: When you create a bean for `LocalContainerEntityManagerFactoryBean` yourself, any customization that was applied during the creation of the auto-configured `LocalContainerEntityManagerFactoryBean` is lost.
|
||||
Make sure to use the auto-configured `EntityManagerFactoryBuilder` to retain JPA and vendor properties.
|
||||
This is particularly important if you were relying on `spring.jpa.*` properties for configuring things like the naming strategy or the DDL mode.
|
||||
|
||||
|
||||
|
||||
[[howto.data-access.use-multiple-entity-managers]]
|
||||
== Using Multiple EntityManagerFactories
|
||||
|
||||
If you need to use JPA against multiple data sources, you likely need one `EntityManagerFactory` per data source.
|
||||
If you need to use JPA against multiple datasources, you likely need one `EntityManagerFactory` per datasource.
|
||||
The `LocalContainerEntityManagerFactoryBean` from Spring ORM allows you to configure an `EntityManagerFactory` for your needs.
|
||||
You can also reuse `JpaProperties` to bind settings for each `EntityManagerFactory`, as shown in the following example:
|
||||
You can also reuse `JpaProperties` to bind settings for a second `EntityManagerFactory`.
|
||||
Building upon xref:how-to:data-access.adoc#howto.data-access.configure-two-datasources[the example for configuring a second `DataSource`], a second `EntityManagerFactory` can be defined as shown in the following example:
|
||||
|
||||
include-code::MyEntityManagerFactoryConfiguration[]
|
||||
include-code::MyAdditionalEntityManagerFactoryConfiguration[]
|
||||
|
||||
The example above creates an `EntityManagerFactory` using a `DataSource` bean named `firstDataSource`.
|
||||
The example above creates an `EntityManagerFactory` using the `DataSource` bean qualified with `@Qualifier("second")`.
|
||||
It scans entities located in the same package as `Order`.
|
||||
It is possible to map additional JPA properties using the `app.first.jpa` namespace.
|
||||
It is possible to map additional JPA properties using the `app.jpa` namespace.
|
||||
The use of `@Bean(defaultCandidate=false)` allows the `secondJpaProperties` and `secondEntityManagerFactory` beans to be defined without interfering with auto-configured beans of the same type.
|
||||
|
||||
NOTE: When you create a bean for `LocalContainerEntityManagerFactoryBean` yourself, any customization that was applied during the creation of the auto-configured `LocalContainerEntityManagerFactoryBean` is lost.
|
||||
Make sure to use the auto-configured `EntityManagerFactoryBuilder` to retain JPA and vendor properties.
|
||||
This is particularly important if you were relying on `spring.jpa.*` properties for configuring things like the naming strategy or the DDL mode.
|
||||
|
||||
You should provide a similar configuration for any additional data sources for which you need JPA access.
|
||||
You should provide a similar configuration for any more additional data sources for which you need JPA access.
|
||||
To complete the picture, you need to configure a `JpaTransactionManager` for each `EntityManagerFactory` as well.
|
||||
Alternatively, you might be able to use a JTA transaction manager that spans both.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
@@ -16,35 +16,20 @@
|
||||
|
||||
package org.springframework.boot.docs.howto.dataaccess.configuretwodatasources;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.jdbc.DataSourceBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class MyDataSourcesConfiguration {
|
||||
public class MyAdditionalDataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConfigurationProperties("app.datasource.first")
|
||||
public DataSourceProperties firstDataSourceProperties() {
|
||||
return new DataSourceProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConfigurationProperties("app.datasource.first.configuration")
|
||||
public HikariDataSource firstDataSource(DataSourceProperties firstDataSourceProperties) {
|
||||
return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("app.datasource.second")
|
||||
@Qualifier("second")
|
||||
@Bean(defaultCandidate = false)
|
||||
@ConfigurationProperties("app.datasource")
|
||||
public BasicDataSource secondDataSource() {
|
||||
return DataSourceBuilder.create().type(BasicDataSource.class).build();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.boot.docs.howto.dataaccess.configuretwodatasources;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -24,33 +23,20 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class MyCompleteDataSourcesConfiguration {
|
||||
public class MyCompleteAdditionalDataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConfigurationProperties("app.datasource.first")
|
||||
public DataSourceProperties firstDataSourceProperties() {
|
||||
return new DataSourceProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConfigurationProperties("app.datasource.first.configuration")
|
||||
public HikariDataSource firstDataSource(DataSourceProperties firstDataSourceProperties) {
|
||||
return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("app.datasource.second")
|
||||
@Qualifier("second")
|
||||
@Bean(defaultCandidate = false)
|
||||
@ConfigurationProperties("app.datasource")
|
||||
public DataSourceProperties secondDataSourceProperties() {
|
||||
return new DataSourceProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("app.datasource.second.configuration")
|
||||
@Qualifier("second")
|
||||
@Bean(defaultCandidate = false)
|
||||
@ConfigurationProperties("app.datasource.configuration")
|
||||
public BasicDataSource secondDataSource(
|
||||
@Qualifier("secondDataSourceProperties") DataSourceProperties secondDataSourceProperties) {
|
||||
return secondDataSourceProperties.initializeDataSourceBuilder().type(BasicDataSource.class).build();
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.boot.docs.howto.dataaccess.usemultipleentitymanagers
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
|
||||
@@ -28,19 +29,21 @@ import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class MyEntityManagerFactoryConfiguration {
|
||||
public class MyAdditionalEntityManagerFactoryConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("app.jpa.first")
|
||||
public JpaProperties firstJpaProperties() {
|
||||
@Qualifier("second")
|
||||
@Bean(defaultCandidate = false)
|
||||
@ConfigurationProperties("app.jpa")
|
||||
public JpaProperties secondJpaProperties() {
|
||||
return new JpaProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory(DataSource firstDataSource,
|
||||
JpaProperties firstJpaProperties) {
|
||||
EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(firstJpaProperties);
|
||||
return builder.dataSource(firstDataSource).packages(Order.class).persistenceUnit("firstDs").build();
|
||||
@Qualifier("second")
|
||||
@Bean(defaultCandidate = false)
|
||||
public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory(@Qualifier("second") DataSource dataSource,
|
||||
@Qualifier("second") JpaProperties jpaProperties) {
|
||||
EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(jpaProperties);
|
||||
return builder.dataSource(dataSource).packages(Order.class).persistenceUnit("second").build();
|
||||
}
|
||||
|
||||
private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties) {
|
||||
@@ -16,6 +16,6 @@
|
||||
|
||||
package org.springframework.boot.docs.howto.dataaccess.usemultipleentitymanagers;
|
||||
|
||||
class Order {
|
||||
public class Order {
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableJpaRepositories(basePackageClasses = Order.class, entityManagerFactoryRef = "firstEntityManagerFactory")
|
||||
@EnableJpaRepositories(basePackageClasses = Order.class, entityManagerFactoryRef = "entityManagerFactory")
|
||||
public class OrderConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
@@ -16,34 +16,20 @@
|
||||
|
||||
package org.springframework.boot.docs.howto.dataaccess.configuretwodatasources
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import org.apache.commons.dbcp2.BasicDataSource
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.boot.jdbc.DataSourceBuilder
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.context.annotation.Primary
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class MyDataSourcesConfiguration {
|
||||
class MyAdditionalDataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConfigurationProperties("app.datasource.first")
|
||||
fun firstDataSourceProperties(): DataSourceProperties {
|
||||
return DataSourceProperties()
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConfigurationProperties("app.datasource.first.configuration")
|
||||
fun firstDataSource(firstDataSourceProperties: DataSourceProperties): HikariDataSource {
|
||||
return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource::class.java).build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("app.datasource.second")
|
||||
@Qualifier("second")
|
||||
@Bean(defaultCandidate = false)
|
||||
@ConfigurationProperties("app.datasource")
|
||||
fun secondDataSource(): BasicDataSource {
|
||||
return DataSourceBuilder.create().type(BasicDataSource::class.java).build()
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
@@ -16,39 +16,27 @@
|
||||
|
||||
package org.springframework.boot.docs.howto.dataaccess.configuretwodatasources
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import org.apache.commons.dbcp2.BasicDataSource
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.context.annotation.Primary
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class MyCompleteDataSourcesConfiguration {
|
||||
class MyCompleteAdditionalDataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConfigurationProperties("app.datasource.first")
|
||||
fun firstDataSourceProperties(): DataSourceProperties {
|
||||
return DataSourceProperties()
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConfigurationProperties("app.datasource.first.configuration")
|
||||
fun firstDataSource(firstDataSourceProperties: DataSourceProperties): HikariDataSource {
|
||||
return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource::class.java).build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("app.datasource.second")
|
||||
@Qualifier("second")
|
||||
@Bean(defaultCandidate = false)
|
||||
@ConfigurationProperties("app.datasource")
|
||||
fun secondDataSourceProperties(): DataSourceProperties {
|
||||
return DataSourceProperties()
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("app.datasource.second.configuration")
|
||||
@Qualifier("second")
|
||||
@Bean(defaultCandidate = false)
|
||||
@ConfigurationProperties("app.datasource.configuration")
|
||||
fun secondDataSource(secondDataSourceProperties: DataSourceProperties): BasicDataSource {
|
||||
return secondDataSourceProperties.initializeDataSourceBuilder().type(BasicDataSource::class.java).build()
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package org.springframework.boot.docs.howto.dataaccess.usemultipleentitymanagers
|
||||
|
||||
import javax.sql.DataSource
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder
|
||||
@@ -29,21 +30,23 @@ import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class MyEntityManagerFactoryConfiguration {
|
||||
class MyAdditionalEntityManagerFactoryConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("app.jpa.first")
|
||||
fun firstJpaProperties(): JpaProperties {
|
||||
@Qualifier("second")
|
||||
@Bean(defaultCandidate = false)
|
||||
@ConfigurationProperties("app.jpa")
|
||||
fun secondJpaProperties(): JpaProperties {
|
||||
return JpaProperties()
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Qualifier("second")
|
||||
@Bean(defaultCandidate = false)
|
||||
fun firstEntityManagerFactory(
|
||||
firstDataSource: DataSource?,
|
||||
firstJpaProperties: JpaProperties
|
||||
@Qualifier("second") dataSource: DataSource,
|
||||
@Qualifier("second") jpaProperties: JpaProperties
|
||||
): LocalContainerEntityManagerFactoryBean {
|
||||
val builder = createEntityManagerFactoryBuilder(firstJpaProperties)
|
||||
return builder.dataSource(firstDataSource).packages(Order::class.java).persistenceUnit("firstDs").build()
|
||||
val builder = createEntityManagerFactoryBuilder(jpaProperties)
|
||||
return builder.dataSource(dataSource).packages(Order::class.java).persistenceUnit("second").build()
|
||||
}
|
||||
|
||||
private fun createEntityManagerFactoryBuilder(jpaProperties: JpaProperties): EntityManagerFactoryBuilder {
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
@@ -21,37 +21,41 @@ import java.sql.SQLException;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link MyCompleteDataSourcesConfiguration}.
|
||||
* Tests for {@link MyCompleteAdditionalDataSourceConfiguration}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@SpringBootTest
|
||||
@Import(MyCompleteDataSourcesConfiguration.class)
|
||||
@Import(MyCompleteAdditionalDataSourceConfiguration.class)
|
||||
class MyCompleteDataSourcesConfigurationTests {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private DataSource dataSource;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("second")
|
||||
private DataSource secondDataSource;
|
||||
|
||||
@Test
|
||||
void validateConfiguration() throws SQLException {
|
||||
assertThat(this.context.getBeansOfType(DataSource.class)).hasSize(2);
|
||||
DataSource dataSource = this.context.getBean(DataSource.class);
|
||||
assertThat(this.context.getBean("firstDataSource")).isSameAs(dataSource);
|
||||
assertThat(dataSource.getConnection().getMetaData().getURL()).startsWith("jdbc:h2:mem:");
|
||||
DataSource secondDataSource = this.context.getBean("secondDataSource", DataSource.class);
|
||||
assertThat(secondDataSource.getConnection().getMetaData().getURL()).startsWith("jdbc:h2:mem:");
|
||||
assertThat(this.context.getBean("dataSource")).isSameAs(this.dataSource);
|
||||
assertThat(this.dataSource.getConnection().getMetaData().getURL()).startsWith("jdbc:h2:mem:");
|
||||
assertThat(this.context.getBean("secondDataSource")).isSameAs(this.secondDataSource);
|
||||
assertThat(this.secondDataSource.getConnection().getMetaData().getURL()).startsWith("jdbc:h2:mem:");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,39 +22,44 @@ import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link MyDataSourcesConfiguration}.
|
||||
* Tests for {@link MyAdditionalDataSourceConfiguration}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@SpringBootTest(properties = { "app.datasource.second.url=jdbc:h2:mem:bar;DB_CLOSE_DELAY=-1",
|
||||
"app.datasource.second.max-total=42" })
|
||||
@Import(MyDataSourcesConfiguration.class)
|
||||
@SpringBootTest(properties = { "app.datasource.url=jdbc:h2:mem:bar;DB_CLOSE_DELAY=-1", "app.datasource.max-total=42" })
|
||||
@Import(MyAdditionalDataSourceConfiguration.class)
|
||||
class MyDataSourcesConfigurationTests {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private DataSource dataSource;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("second")
|
||||
private DataSource secondDataSource;
|
||||
|
||||
@Test
|
||||
void validateConfiguration() throws SQLException {
|
||||
assertThat(this.context.getBeansOfType(DataSource.class)).hasSize(2);
|
||||
DataSource dataSource = this.context.getBean(DataSource.class);
|
||||
assertThat(this.context.getBean("firstDataSource")).isSameAs(dataSource);
|
||||
assertThat(dataSource.getConnection().getMetaData().getURL()).startsWith("jdbc:h2:mem:");
|
||||
BasicDataSource secondDataSource = this.context.getBean("secondDataSource", BasicDataSource.class);
|
||||
assertThat(secondDataSource.getUrl()).isEqualTo("jdbc:h2:mem:bar;DB_CLOSE_DELAY=-1");
|
||||
assertThat(secondDataSource.getMaxTotal()).isEqualTo(42);
|
||||
assertThat(this.context.getBean("dataSource")).isSameAs(this.dataSource);
|
||||
assertThat(this.dataSource.getConnection().getMetaData().getURL()).startsWith("jdbc:h2:mem:");
|
||||
assertThat(this.context.getBean("secondDataSource")).isSameAs(this.secondDataSource);
|
||||
assertThat(this.secondDataSource).extracting((dataSource) -> ((BasicDataSource) dataSource).getUrl())
|
||||
.isEqualTo("jdbc:h2:mem:bar;DB_CLOSE_DELAY=-1");
|
||||
assertThat(this.secondDataSource).extracting((dataSource) -> ((BasicDataSource) dataSource).getMaxTotal())
|
||||
.isEqualTo(42);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user