← Назад к вопросам

Как в Spring ограничить создание бина, чтобы при старте приложения инициализировался только один из нескольких DataSource

3.0 Senior🔥 71 комментариев
#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ: Селективная инициализация DataSource в Spring

Вопрос касается того, как в Spring выбрать, какой из нескольких DataSource бинов должен быть инициализирован при старте приложения. Это решается несколькими способами.

Проблема

Представим, что у вас есть несколько конфигураций DataSource, но вы хотите использовать только одну в зависимости от окружения (dev, staging, prod) или другого условия.

// ❌ Проблема: оба DataSource будут инициализированы
@Configuration
public class DataSourceConfig {
    
    @Bean
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:mysql://primary-db:3306/mydb")
            .username("user")
            .password("password")
            .build();
    }
    
    @Bean
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:mysql://secondary-db:3306/mydb")
            .username("user")
            .password("password")
            .build();
    }
}

Оба DataSource будут инициализированы и попытаются подключиться к БД при старте приложения.

Решение 1: @ConditionalOnProperty

@Configuration
public class DataSourceConfig {
    
    // Инициализируется только если app.datasource.type = primary
    @Bean
    @ConditionalOnProperty(
        name = "app.datasource.type",
        havingValue = "primary",
        matchIfMissing = true  // Если свойство не указано, использовать primary
    )
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create()
            .url("${spring.datasource.primary.url}")
            .username("${spring.datasource.primary.username}")
            .password("${spring.datasource.primary.password}")
            .build();
    }
    
    // Инициализируется только если app.datasource.type = secondary
    @Bean
    @ConditionalOnProperty(
        name = "app.datasource.type",
        havingValue = "secondary"
    )
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create()
            .url("${spring.datasource.secondary.url}")
            .username("${spring.datasource.secondary.username}")
            .password("${spring.datasource.secondary.password}")
            .build();
    }
}

// application.properties
// app.datasource.type=primary
// spring.datasource.primary.url=jdbc:mysql://db1:3306/app
// spring.datasource.primary.username=user1
// spring.datasource.primary.password=pass1

// application-staging.properties
// app.datasource.type=secondary
// spring.datasource.secondary.url=jdbc:mysql://db2:3306/app
// spring.datasource.secondary.username=user2
// spring.datasource.secondary.password=pass2

Решение 2: @ConditionalOnExpression

@Configuration
public class DataSourceConfig {
    
    // Используется при сложной логике
    @Bean
    @ConditionalOnExpression("'${spring.profiles.active}' == 'prod'")
    public DataSource productionDataSource() {
        return createDataSource(
            "jdbc:mysql://prod-db:3306/myapp",
            "produser",
            "${DB_PASSWORD}" // Из переменной окружения
        );
    }
    
    @Bean
    @ConditionalOnExpression("'${spring.profiles.active}' != 'prod'")
    public DataSource developmentDataSource() {
        return createDataSource(
            "jdbc:h2:mem:db",
            "sa",
            ""
        );
    }
    
    private DataSource createDataSource(String url, String user, String password) {
        return DataSourceBuilder.create()
            .url(url)
            .username(user)
            .password(password)
            .build();
    }
}

// application.properties
// spring.profiles.active=dev

// application-prod.properties
// spring.profiles.active=prod

Решение 3: Использование Profiles

Это самый «Spring way» подход.

// Конфигурация для профиля "dev"
@Configuration
@Profile("dev")
public class DevDataSourceConfig {
    
    @Bean
    public DataSource dataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:h2:mem:devdb")
            .username("sa")
            .password("")
            .build();
    }
}

// Конфигурация для профиля "prod"
@Configuration
@Profile("prod")
public class ProdDataSourceConfig {
    
    @Bean
    public DataSource dataSource(
        @Value("${db.prod.url}") String url,
        @Value("${db.prod.username}") String username,
        @Value("${db.prod.password}") String password
    ) {
        return DataSourceBuilder.create()
            .url(url)
            .username(username)
            .password(password)
            .build();
    }
}

// Конфигурация для профиля "staging"
@Configuration
@Profile("staging")
public class StagingDataSourceConfig {
    
    @Bean
    public DataSource dataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:mysql://staging-db:3306/myapp")
            .username("staging_user")
            .password("${STAGING_DB_PASSWORD}")
            .build();
    }
}

// application.properties
// spring.profiles.active=prod

// Или через env переменные
// SPRING_PROFILES_ACTIVE=staging

Решение 4: @Conditional с Custom Condition

Для более сложной логики:

// Кастомное условие
public class IsPrimaryDatabaseCondition implements Condition {
    
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // Читаем конфигурацию и проверяем условие
        String datasourceType = context.getEnvironment()
            .getProperty("app.datasource.type");
        return "primary".equals(datasourceType);
    }
}

public class IsSecondaryDatabaseCondition implements Condition {
    
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String datasourceType = context.getEnvironment()
            .getProperty("app.datasource.type");
        return "secondary".equals(datasourceType);
    }
}

// Использование кастомного условия
@Configuration
public class DataSourceConfig {
    
    @Bean
    @Conditional(IsPrimaryDatabaseCondition.class)
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:mysql://primary:3306/db")
            .build();
    }
    
    @Bean
    @Conditional(IsSecondaryDatabaseCondition.class)
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:mysql://secondary:3306/db")
            .build();
    }
}

Решение 5: Factory Bean с условием

@Configuration
public class DataSourceConfig {
    
    @Bean
    public DataSource dataSource(
        @Value("${app.datasource.type:primary}") String type,
        @Value("${spring.datasource.url}") String url,
        @Value("${spring.datasource.username}") String username,
        @Value("${spring.datasource.password}") String password
    ) {
        DataSourceBuilder<?> builder = DataSourceBuilder.create();
        
        switch (type) {
            case "primary":
                return builder
                    .url(url)
                    .username(username)
                    .password(password)
                    .build();
                    
            case "secondary":
                return builder
                    .url("jdbc:mysql://secondary:3306/db")
                    .username("user2")
                    .password("pass2")
                    .build();
                    
            default:
                throw new IllegalArgumentException("Unknown datasource type: " + type);
        }
    }
}

// Это простой способ, но инициализирует все параметры БД при создании бина

Решение 6: Ленивая инициализация

@Configuration
public class DataSourceConfig {
    
    @Bean
    @Lazy  // Бин инициализируется только когда его запросят
    @ConditionalOnProperty(name = "app.datasource.type", havingValue = "primary")
    public DataSource primaryDataSource() {
        System.out.println("Initializing Primary DataSource");
        return DataSourceBuilder.create()
            .url("jdbc:mysql://primary:3306/db")
            .build();
    }
    
    @Bean
    @Lazy
    @ConditionalOnProperty(name = "app.datasource.type", havingValue = "secondary")
    public DataSource secondaryDataSource() {
        System.out.println("Initializing Secondary DataSource");
        return DataSourceBuilder.create()
            .url("jdbc:mysql://secondary:3306/db")
            .build();
    }
}

Практический пример: Multi-tenant приложение

@Configuration
public class MultiTenantDataSourceConfig {
    
    @Bean
    public DataSource dataSource(
        @Value("${app.tenant:default}") String tenant,
        @Value("${spring.datasource.url}") String url,
        @Value("${spring.datasource.username}") String username,
        @Value("${spring.datasource.password}") String password
    ) {
        // Выбираем URL в зависимости от tenant
        String tenantUrl = url.replace("{tenant}", tenant);
        
        return DataSourceBuilder.create()
            .url(tenantUrl)
            .username(username)
            .password(password)
            .hikariConfig(hikariConfig())
            .build();
    }
    
    private HikariConfig hikariConfig() {
        HikariConfig config = new HikariConfig();
        config.setMaximumPoolSize(10);
        config.setMinimumIdle(5);
        config.setConnectionTimeout(10000);
        return config;
    }
}

// application.properties
// spring.datasource.url=jdbc:mysql://db-{tenant}.example.com:3306/app
// spring.datasource.username=app_user
// spring.datasource.password=${DB_PASSWORD}
// app.tenant=customer1

Сравнение подходов

┌──────────────────────────┬──────────┬──────────┬─────────────────┐
│ Подход                   │ Гибкость │ Сложность│ Рекомендуется   │
├──────────────────────────┼──────────┼──────────┼─────────────────┤
│ @ConditionalOnProperty   │ ⭐⭐⭐  │ ⭐      │ Для простых      │
│ @Profile                 │ ⭐⭐    │ ⭐      │ Для окружений    │
│ @ConditionalOnExpression │ ⭐⭐⭐⭐ │ ⭐⭐   │ Сложная логика  │
│ @Conditional             │ ⭐⭐⭐⭐⭐│ ⭐⭐⭐ │ Очень сложная   │
│ Factory Bean             │ ⭐⭐    │ ⭐⭐   │ Не рекомендуется│
│ @Lazy                    │ ⭐      │ ⭐      │ Дополнение      │
└──────────────────────────┴──────────┴──────────┴─────────────────┘

Лучшая практика

// ✅ Рекомендуемый подход для большинства случаев
@Configuration
public class DataSourceConfig {
    
    @Bean
    @ConditionalOnProperty(
        name = "app.db.enabled",
        havingValue = "true",
        matchIfMissing = true
    )
    public DataSource dataSource(
        @Value("${spring.datasource.url}") String url,
        @Value("${spring.datasource.username}") String username,
        @Value("${spring.datasource.password}") String password
    ) {
        return DataSourceBuilder.create()
            .url(url)
            .username(username)
            .password(password)
            .build();
    }
    
    @Bean
    @ConditionalOnMissingBean(DataSource.class)
    public DataSource defaultDataSource() {
        // Fallback: в памяти БД для тестирования
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }
}

// application.properties
// spring.datasource.url=jdbc:mysql://localhost:3306/app
// spring.datasource.username=root
// spring.datasource.password=password
// app.db.enabled=true

Заключение

Для селективной инициализации DataSource в Spring используйте:

  1. @ConditionalOnProperty — для простого выбора по свойствам
  2. @Profile — для разных окружений (dev, staging, prod)
  3. @ConditionalOnExpression — для более сложной логики
  4. @Conditional — для кастомной логики
  5. @Lazy — для отложенной инициализации

Много DataSource бинов инициализируются, только если их условие выполняется (указано в @ConditionalOnProperty или других аннотациях). Spring не создаст их, если условие false.