javaspring-bootdatasourceconnection-pooling

Spring boot connection pool size with custom datasource


I have multiple Spring Boot 3.4.4 applications, connecting to various databases. Mostly I'm using the default spring datasource configuration in the application.yml, and I could setup the connection pool size like so:

spring:
  datasource:
    hikari:
      maximum-pool-size: 10
      idle-timeout: 20000
      minimum-idle: 1
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://ip:port/dbname
    username: user1
    password: password1

This works as expected when using the default spring datasource. However for some apps I need access to multiple databases, and this doesn't work. In such cases I use the default spring datasource yaml config (same as above) for one datasource, and another similar yaml structure for another datasource, e.g:

spring:
  datasource:
    hikari:
      maximum-pool-size: 10
      idle-timeout: 20000
      minimum-idle: 1
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://ip:port/dbname
    username: user1
    password: password1

  datasource2:
    hikari:
      maximum-pool-size: 10
      idle-timeout: 20000
      minimum-idle: 1
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://ip:port/dbname2
    username: user2
    password: password2

I initialize these from Java with a custom class:

@Configuration
@EnableJpaRepositories(
        basePackages = {"my.packages.repository.datasource1"},
        entityManagerFactoryRef = "myEntityManagerFactory",
        transactionManagerRef = "myTransactionManager"
)
@EnableTransactionManagement
@RequiredArgsConstructor
public class MyDbConfig {

    @Primary
    @Bean("myDataSourceProperties")
    @ConfigurationProperties("spring.datasource")
    DataSourceProperties myDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Primary
    @Bean(name = "myDataSource")
    DataSource myDataSource(@Qualifier("myDataSourceProperties") DataSourceProperties myDataSourceProperties) {
        return myDataSourceProperties.initializeDataSourceBuilder().build();
    }

    @Primary
    @Bean(name = "myEntityManagerFactory")
    LocalContainerEntityManagerFactoryBean myEntityManagerFactory(
            final EntityManagerFactoryBuilder builder,
            @Qualifier("myDataSource") DataSource dataSource,
            JpaProperties jpaProperties) {
        return builder.dataSource(dataSource)
                .packages("my.packages.domain.datasource1")
                .jta(false)
                .persistenceUnit("my-datasource1")
                .properties(jpaProperties.getProperties())
                .properties(Map.of(
                        "hibernate.physical_naming_strategy", "org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy",
                        "hibernate.implicit_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy",
                        "hibernate.id.new_generator_mappings", "true",
                        "hibernate.transaction.jta.platform", "org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform"
                ))
                .build();
    }

    @Primary
    @Bean(name = "myTransactionManager")
    PlatformTransactionManager myTransactionManager(@Qualifier("myEntityManagerFactory")
                                                            EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

I have multiple versions of the above class, one for each datasource. I separate my repository and entity classes into different packages for each datasource, and then I assign the appropriate package to each datasource definition. It works correctly, but it keeps opening the default 10 connections, not respecting the minimum-idle 1 setting from the configuration. The default for HikariCP is that the minimum is same as maximum, so that's why, I assume.

I read through multiple articles and stackoverflow questions, but almost exclusively everyone always focuses on the default settings, which is understandable, but that works for me too. I need help for these custom datasources, and I haven't found it so far.

This is the relevant logs that I get when the app is starting:

[           main] c.z.h.HikariDataSource                   : HikariPool-1 - Starting...
[           main] c.z.h.p.HikariPool                       : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@20a9fb01
[           main] c.z.h.HikariDataSource                   : HikariPool-1 - Start completed.
[           main] o.h.j.i.u.LogHelper                      : HHH000204: Processing PersistenceUnitInfo [name: my-datasource1]
[           main] o.h.Version                              : HHH000412: Hibernate ORM core version 6.6.11.Final
[           main] o.h.c.i.RegionFactoryInitiator           : HHH000026: Second-level cache disabled
[           main] o.s.b.w.e.t.TomcatWebServer              : Tomcat initialized with port 8080 (http)
[           main] o.a.c.c.StandardService                  : Starting service [Tomcat]
[           main] o.a.c.c.StandardEngine                   : Starting Servlet engine: [Apache Tomcat/10.1.39]
[           main] o.a.c.c.C.[.[.[/]                        : Initializing Spring embedded WebApplicationContext
[           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 19839 ms
[           main] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
[           main] o.h.o.c.pooling                          : HHH10001005: Database info:
    Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)']
    Database driver: undefined/unknown
    Database version: 17.0
    Autocommit mode: undefined/unknown
    Isolation level: undefined/unknown
    Minimum pool size: undefined/unknown
    Maximum pool size: undefined/unknown

And of course similar for HikariPool-2 for the other datasource.


Solution

  • Note: If the bean name is the same as the method name, you can just use @Bean

    I tend to use HikariConfig instead of DataSourceProperties eg:

    import com.zaxxer.hikari.HikariConfig;
    import com.zaxxer.hikari.HikariDataSource;
    
    @Configuration
    public class MyConfiguration() {
    
       @Bean
       @ConfigurationProperties("spring.datasource1")
       public HikariConfig datasource1Config() {
          return new HikariConfig();
       }
    
       @Bean
       public DataSource datasource1(@Qualifier("datasource1Config") HikariConfig config1) {
          return new HikariDataSource(config1);
       }
    
       @Bean
       @ConfigurationProperties("spring.datasource2")
       public HikariConfig datasource2Config() {
          return new HikariConfig();
       }
    
       @Bean
       public DataSource datasource2(@Qualifier("datasource2Config") HikariConfig config2) {
          return new HikariDataSource(config1);
       }
    }
    

    Then your yaml would look something like

    spring:
      datasource1:
        maximum-pool-size: 10
        idle-timeout: 20000
        minimum-idle: 1
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://ip:port/dbname
        username: user1
        password: password1
      datasource2:
        maximum-pool-size: 10
        idle-timeout: 20000
        minimum-idle: 1
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://ip:port/dbname2
        username: user2
        password: password2
    

    Note also that if you have lots of datasources the @Qualifier annotations start getting quite unwieldy to maintain. In these cases you can make your own custom qualifier annotations. Eg:

    @Target({FIELD, PARAMETER, TYPE, METHOD})
    @Retention(RUNTIME)
    @Qualifier
    public @interface Datasource1 { }
    
    @Target({FIELD, PARAMETER, TYPE, METHOD})
    @Retention(RUNTIME)
    @Qualifier
    public @interface Datasource2 { }
    
    import com.zaxxer.hikari.HikariConfig;
    import com.zaxxer.hikari.HikariDataSource;
    
    @Configuration
    public class MyConfiguration() {
    
       @Bean
       @Datasource1 
       @ConfigurationProperties("spring.datasource1")
       public HikariConfig datasource1Config() {
          return new HikariConfig();
       }
    
       @Bean
       @Datasource1
       public DataSource datasource1(@Datasource1 HikariConfig config) {
          return new HikariDataSource(config);
       }
    
       @Bean
       @Datasource1
       public MyService service1(@Datasource1 datasource) {
          return new MyService(datasource);
       }
    
       @Bean
       @Datasource2 
       @ConfigurationProperties("spring.datasource2")
       public HikariConfig datasource2Config() {
          return new HikariConfig();
       }
    
       @Bean
       @Datasource2
       public DataSource datasource2(@Datasource2 HikariConfig config) {
          return new HikariDataSource(config);
       }
    
       @Bean
       @Datasource2
       public MyService service2(@Datasource2 datasource) {
          return new MyService(datasource);
       }
    }
    

    See also https://www.javabyexamples.com/spring-custom-qualifier-annotation