The documentation for Spring Boot 3.4 now recommends the @Qualifier("xyz") and @Bean(defaultCandidate = false) approach to define multiple DataSources: https://docs.spring.io/spring-boot/3.4/how-to/data-access.html#howto.data-access.configure-two-datasources
At first glance, this approach is neat because the first DataSource is set up by auto-configuration as if there were no second DataSource. However, the new approach has some probably unwanted side-effects that users should be made aware of.
With the previous approach (see https://docs.spring.io/spring-boot/3.3/how-to/data-access.html#howto.data-access.configure-two-datasources), the additional DataSources would still participate in other auto-configurations such as DataSourcePoolMetricsAutoConfiguration and DataSourceHealthContributorAutoConfiguration. That is no longer the case with the new approach, because an additional DataSource with defaultCandidate = false is neither present in Map<String, DataSource> nor is it available via ObjectProvider<DataSource>.
I leave it to the subject matter experts to decide what should be done:
- Update documentation to mention the limitations of the new approach
- Revert documentation to previous approach
- Fix other auto-configurations that apply to a "list" of DataSources of some sort (such as
ObjectProvider<DataSource>)—not sure if that is even possible or a good idea for that matter
Comment From: quaff
Fix other auto-configurations that apply to a "list" of DataSources of some sort (such as
ObjectProvider<DataSource>)
ObjectProvider<DataSource> filter out non-defaultCandidate beans, we could use beanFactory.getBeansOfType(DataSource.class) instead of injection of Map<String, DataSource>.
Comment From: quaff
Fix other auto-configurations that apply to a "list" of DataSources of some sort (such as
ObjectProvider<DataSource>)
ObjectProvider<DataSource>filter out non-defaultCandidate beans, we could usebeanFactory.getBeansOfType(DataSource.class)instead of injection ofMap<String, DataSource>.
I'd like to prepare an PR if the team accept this solution, see https://github.com/spring-projects/spring-boot/compare/main...quaff:patch-108?expand=1.
Comment From: philwebb
I'm not a fan of using the BeanFactory directly, but I can't immediately think of a better option. @jhoeller Any thoughts on the best way to inject something that will include non-defaultCandidate beans? Perhaps we need some new methods on ObjectProvider or a new annotation to signal intent?
Comment From: quaff
I'm not a fan of using the
BeanFactorydirectly, but I can't immediately think of a better option. @jhoeller Any thoughts on the best way to inject something that will include non-defaultCandidate beans? Perhaps we need some new methods onObjectProvideror a new annotation to signal intent?
I vote for new methods on ObjectProvider, It's not good to introduce new annotation, make things more complex hard to understand.
Comment From: jhoeller
The intent was for such general container lookups to happen via beanFactory.getBeansOfType(DataSource.class) indeed, if necessary. defaultCandidate=false restricts user injection without qualifiers, and if framework components declare user-style injection points with those semantics, they get the same restricted view.
We could introduce new methods on ObjectProvider but it would blur the line with the user's view, so I would only consider that if it was a common need in user components. If defaultCandidate=false has unwanted side effects in a scenario, it might be the wrong choice. That said, for framework facilities, it's just a matter of using the appropriate lookup mechanism. In framework components, I don't consider direct interaction with an injected BeanFactory reference as inferior to ObjectProvider interaction.
Comment From: philwebb
Holding to the new year as we consider options
Comment From: snicoll
This issue is now blocked by https://github.com/spring-projects/spring-framework/issues/34203. If implemented we'd hopefully be able to restore the previous behavior with such beans.
Comment From: quaff
This issue is now blocked by spring-projects/spring-framework#34203. If implemented we'd hopefully be able to restore the previous behavior with such beans.
Spring Framework provide ObjectProvider.stream(Predicate<Class<?>>) now, but it's not enough, Spring Boot requires bean names also.
Comment From: wilkinsona
Thanks, @quaff, but we're already well aware of that. We're working through this with the Framework team using https://github.com/wilkinsona/spring-boot/commit/2a88c4ae55d3e8054914f4fc8b4c3a7e2662b468 as a starting point for the discussion.
Comment From: mdaepp
@wilkinsona Thanks for fixing this! I think you forgot to adapt HikariDataSourceMetricsConfiguration org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration. It is implemented slightly differently as it uses ObjectProvider<DataSource> dataSources. If I am not mistaken, ObjectProvider still doesn't see non-default candidates. As-is, MicrometerMetricsTrackerFactory will not be registered for DataSources with defaultCandidate=false.