Как при объявлении нескольких datasource бинов в Spring сделать так, чтобы клиентский код использовал один datasource без изменений
Что проверяют
Знаете ли вы, как Spring выбирает бин при внедрении по типу и что происходит с автоконфигурацией Spring Boot, когда бинов DataSource становится два. Следом спрашивают про транзакции и JPA для второго источника или про маршрутизацию между базами.
Как отвечать
Проблема: код внедряет DataSource по типу, и при двух кандидатах контекст падает с NoUniqueBeanDefinitionException. Решения:
@Primaryна основном бине. Внедрение по типу получает его, а второй берут явно через@Qualifier("reportingDataSource"). Клиентский код не меняется. АвтоконфигурацииJdbcTemplate, JPA и транзакций в Boot срабатывают только при единственном кандидате (@ConditionalOnSingleCandidate), и@Primaryкак раз делает кандидата единственным;@Bean(defaultCandidate = false)из Spring Framework 6.2: второй бин не участвует во внедрении по типу, его получают только по квалификатору. Удобно, когда дополнительный источник нужен паре компонентов;AbstractRoutingDataSource: если нужно переключаться между базами на лету (чтение с реплики, запись в мастер, мультитенантность). Клиенту отдаётся один бин, аdetermineCurrentLookupKey()по значению изThreadLocalили поTransactionSynchronizationManager.isCurrentTransactionReadOnly()выбирает реальный источник. Оберните его вLazyConnectionDataSourceProxy, иначе соединение берётся раньше, чем становится известен признак readOnly.
Отметьте, что свой бин DataSource отключает автоконфигурацию пула из spring.datasource.*, поэтому свойства привязывают вручную через @ConfigurationProperties и DataSourceProperties.
Где спотыкаются
Пишут обёртку-провайдер, которая возвращает нужный источник. Это как раз изменение клиентского кода: сервисы начинают зависеть от провайдера вместо DataSource.
Ставят @Primary, но забывают, что второй базе нужны свои EntityManagerFactory и PlatformTransactionManager. Иначе @Transactional молча работает только с основной.
Используют AbstractRoutingDataSource без LazyConnectionDataSourceProxy и удивляются, что @Transactional(readOnly = true) всё равно ходит в мастер.