← Назад к вопросам
Как в Spring Boot задать БД по умолчанию при наличии нескольких DataSource
2.3 Middle🔥 151 комментариев
#Spring Boot и Spring Data
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Несколько DataSource в Spring Boot и выбор основного
Этот сценарий встречается, когда приложение работает с несколькими базами данных. Spring нужно явно сказать, какая из них основная.
1. Проблема: Ambiguous DataSource
По умолчанию Spring Boot создаёт один DataSource из application.properties/yml. Но если их несколько:
# application.yml
spring:
datasource:
primary:
url: jdbc:postgresql://localhost:5432/app_db
username: app_user
password: secret
driver-class-name: org.postgresql.Driver
replica:
url: jdbc:postgresql://replica.db:5432/app_db
username: app_user
password: secret
driver-class-name: org.postgresql.Driver
analytics:
url: jdbc:mysql://analytics.db:3306/analytics
username: analytics_user
password: secret
driver-class-name: com.mysql.cj.jdbc.Driver
Спринг не знает, какой использовать:
Error: NoUniqueBeanDefinitionException:
No qualifying bean of type 'javax.sql.DataSource' is available:
expected single matching bean but found 3: primaryDataSource, replicaDataSource, analyticsDataSource
2. Решение 1: @Primary аннотация
Простой и рекомендуемый подход — отметить основной DataSource:
@Configuration
public class DataSourceConfiguration {
// Основная БД
@Primary // ← ЭТО ГЛАВНОЕ!
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
// Replica — only for read
@Bean(name = "replicaDataSource")
@ConfigurationProperties(prefix = "spring.datasource.replica")
public DataSource replicaDataSource() {
return DataSourceBuilder.create().build();
}
// Analytics — отдельная БД
@Bean(name = "analyticsDataSource")
@ConfigurationProperties(prefix = "spring.datasource.analytics")
public DataSource analyticsDataSource() {
return DataSourceBuilder.create().build();
}
}
Теперь Spring знает какой использовать по умолчанию:
@Repository
public class UserRepository {
@Autowired
private DataSource dataSource; // Получит primaryDataSource (благодаря @Primary)
public User findById(UUID id) {
// Используется основная БД
return new JdbcTemplate(dataSource).queryForObject(
"SELECT * FROM users WHERE id = ?",
new UserRowMapper(),
id
);
}
}
3. Решение 2: Явное указание через @Qualifier
Если нужно выбрать конкретный DataSource:
@Service
public class AnalyticsService {
// Явно указываем, какой DataSource использовать
@Autowired
@Qualifier("analyticsDataSource")
private DataSource analyticsDataSource;
public void saveMetric(String metric, long value) {
JdbcTemplate template = new JdbcTemplate(analyticsDataSource);
template.update(
"INSERT INTO metrics (name, value) VALUES (?, ?)",
metric, value
);
}
}
@Service
public class UserService {
// Явно указываем primary
@Autowired
@Qualifier("primaryDataSource")
private DataSource dataSource;
public void createUser(User user) {
// Используется только основная БД
userRepository.save(user);
}
}
4. Решение 3: Несколько EntityManagers (JPA)
Для JPA нужен более сложный setup:
@Configuration
@EnableTransactionManagement
public class JpaConfiguration {
// Основная БД
@Primary
@Bean
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
@Qualifier("primaryDataSource") DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.example.domain");
JpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(adapter);
Properties props = new Properties();
props.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
props.setProperty("hibernate.hbm2ddl.auto", "validate");
em.setJpaProperties(props);
return em;
}
@Primary
@Bean
public PlatformTransactionManager primaryTransactionManager(
@Qualifier("primaryEntityManagerFactory") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
// ===== Replica DataSource =====
@Bean
@ConfigurationProperties(prefix = "spring.datasource.replica")
public DataSource replicaDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public LocalContainerEntityManagerFactoryBean replicaEntityManagerFactory(
@Qualifier("replicaDataSource") DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.example.domain");
JpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(adapter);
return em;
}
@Bean
public PlatformTransactionManager replicaTransactionManager(
@Qualifier("replicaEntityManagerFactory") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
Ломбардирование с несколькими EntityManagers требует использования разных пакетов для сущностей:
@Transactional("primaryTransactionManager")
@Repository
public class UserRepository extends JpaRepository<User, UUID> {
// Использует primaryEntityManagerFactory
}
@Transactional("replicaTransactionManager")
@Repository
public class UserReplicaRepository extends JpaRepository<User, UUID> {
// Использует replicaEntityManagerFactory (read-only)
}
5. Практический пример: Routing
Часто нужно маршрутизировать запросы:
@Component
public class DataSourceRouter extends AbstractRoutingDataSource {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
public static void clearDataSourceKey() {
contextHolder.remove();
}
@Override
protected Object determineCurrentLookupKey() {
return contextHolder.get();
}
}
@Configuration
public class DataSourceConfiguration {
@Bean
public DataSource primaryDataSource() {
return DataSourceBuilder.create()
.url("jdbc:postgresql://localhost:5432/primary")
.username("user")
.password("pass")
.build();
}
@Bean
public DataSource replicaDataSource() {
return DataSourceBuilder.create()
.url("jdbc:postgresql://replica:5432/primary")
.username("user")
.password("pass")
.build();
}
@Bean
@Primary
public DataSource routingDataSource(
@Qualifier("primaryDataSource") DataSource primary,
@Qualifier("replicaDataSource") DataSource replica) {
DataSourceRouter router = new DataSourceRouter();
Map<Object, Object> sources = new HashMap<>();
sources.put("primary", primary);
sources.put("replica", replica);
router.setTargetDataSources(sources);
router.setDefaultTargetDataSource(primary);
return router;
}
}
// Использование
@Service
public class UserService {
@Autowired
private UserRepository repository;
// Чтение — используем replica
@Transactional(readOnly = true)
public User findById(UUID id) {
DataSourceRouter.setDataSourceKey("replica");
try {
return repository.findById(id).orElse(null);
} finally {
DataSourceRouter.clearDataSourceKey();
}
}
// Запись — используем primary
@Transactional
public void save(User user) {
DataSourceRouter.setDataSourceKey("primary");
try {
repository.save(user);
} finally {
DataSourceRouter.clearDataSourceKey();
}
}
}
6. Best Practices
// ✅ ПРАВИЛЬНО
@Configuration
public class DataSourceConfig {
@Primary // Явно отмечаем основную
@Bean
public DataSource primaryDataSource() { }
@Bean
public DataSource secondaryDataSource() { }
}
// ❌ ПЛОХО — неясно, какая основная
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource1() { }
@Bean
public DataSource dataSource2() { }
}
// ✅ ПРАВИЛЬНО — явно указываем
@Service
public class MyService {
@Autowired
@Qualifier("analyticsDataSource")
private DataSource analyticsDs;
}
// ❌ ПЛОХО — неясно, какой DataSource внедрится
@Service
public class MyService {
@Autowired
private DataSource dataSource;
}
Выводы
- @Primary — указывает основной DataSource для автоматического внедрения
- @Qualifier — явно указывает, какой конкретный DataSource использовать
- AbstractRoutingDataSource — динамическая маршрутизация между БД
- Несколько EntityManagers — нужны для JPA с разными БД
- ThreadLocal — важен для правильной маршрутизации в многопоточном окружении
Обычно используется комбинация: @Primary для основного, @Qualifier для остальных.