【问题标题】:How to setup multiple connection pools when multiple datasources are used in Spring Boot?Spring Boot中使用多个数据源时如何设置多个连接池?
【发布时间】:2018-02-03 05:04:27
【问题描述】:

我有一个连接到两个独立数据库的 Spring Boot 应用程序。一切正常(我按照the docsa tutorial 中的步骤操作),尽管为了自定义Tomcat JDBC 连接池设置,I had to manually configure it(因为通过定义多个数据源,忽略了启动自动配置,并且Spring Boot 不再从 application.properties 读取特定于 tomcat 的属性。

当我在配置两个 DataSource 期间使用调试器时,我看到两个 org.apache.tomcat.jdbc.pool.DataSource 实例在 DataSource.PoolProperties["name"] 条目中具有相同的连接池。 请参阅下面的调试器屏幕截图,每个 dataSource() 方法都在单独的配置类中配置。请注意,定义了相同的连接池。

但是,从我使用 jConsole + tomcat JMX 看到的情况来看,只有一个连接池,其中配置了主数据库详细信息(URL、凭据,见下文)。

由于 Spring 内部有多层抽象,我很难调试这个。我有Eclipse Class Decompiler plugin,我通常用它来查看 Spring 逻辑,但在这种情况下,数据源的初始化代码发生在注册 bean 时,而不是在 Spring Boot 实际使用它们来设置数据源时起来。

底线,你能帮我理解吗:

  1. 为什么只有一个连接池
  2. 如何使用两个连接池,一个用于每个数据源
  3. 查看 Spring 代码的何处以了解有关其工作原理的更多详细信息

对于第二个问题,有点related question,但没有答案。有another question是误报,another one是与Spring相关的,不是Spring Boot,所以请不要举报为骗子。

【问题讨论】:

  • 能否附上完整的数据源创建方法?图片的边缘被裁剪。

标签: java spring tomcat spring-boot spring-data-jpa


【解决方案1】:
  1. 一般DataSource接口是通过池化库实现的,兼容框架和JavaEE代码作为通用JDBC连接源,实际与DB驱动配合使用。
    SpringBoot 自动配置具有用于流行池库的 DataSource 初始化程序。您可以在Spring sources 中找到完整列表。
    这意味着您需要在项目中利用池化,只需添加像 Hikari 这样的池化库作为依赖项并配置 spring.datasource.* 参数。 Spring 将创建和配置 singleDataSource 可以在您的代码中自动装配。
  2. 如果您需要创建多个DataSource,则另当别论。 SpringBoot 自动配置大量使用@ConditionalOnMissingBean 注释来确定可以应用默认行为的情况。 Spring 无法创建两个默认数据源,因为它不明确应该使用哪一个。
    您可以在 spring-boot-autoconfugire 模块中找到它:Spring 仅在上下文中没有这种类型的 bean 时才会启动 DataSource 初始化逻辑。
    要使用多个池,您必须为每个池化数据库连接定义自己的 Bean。 Spring 会注意到您的 DataSource 并且不会在内部创建池。这是example
  3. 您可以找到有关DataSource 自动配置here 的更多详细信息

【讨论】:

  • 感谢和抱歉迟到的回复。在上面的示例中,我创建了两个不同的数据源,但它们没有被 Spring 拾取,只有一个被拾取。
【解决方案2】:

我正在回答我当时的所作所为。如果您找到更好的解决方案或 Spring 将允许多个连接池,请发布答案,我会选择您的。

因为Spring会配置,给定我在问题中发布的代码,只有一个连接池(在tomcat CP上设置validationQueryvalidationInterval),我添加了使我的第二个数据源保持活动状态的预定方法。

@Scheduled(fixedRate=INTERVAL_IN_MS)
public void scheduledTestDatabaseConnection() {
    try {
        testDatabaseConnection();
        LOGGER.trace("Tested EJBCA DB connection with success");
    }
    catch (Exception e) {
        LOGGER.error("Got an error when refreshing the EJBCA DB connection '{}'", e.getMessage());
    }
}

在上面的例子中,testDatabaseConnection() 调用了 Spring Data Repository 上的一个方法

@Query("SELECT 1 FROM MyTable")
public int testConnection();

【讨论】:

    【解决方案3】:

    这是我必须采用的方法,以便为每个数据源获取单独的池。以下是@user3007501上面提出的几点的实现。

    1. 不要使用DataSourceBuilder,而是创建org.apache.tomcat.jdbc.pool.DataSource。这将创建池并配置连接。

      如果您需要HikariDbcp2,请将下面的方法createPooledDataSource() 的内容替换为来自原始Spring 源DataSourceConfiguration.javaHikariDbcp2 配置部分。下面createPooledDataSource()显示的内容是从链接文件中的Tomcat.dataSource()方法盗取的。

    2. application.yml 中的datasource 配置的每个 下添加tomcat 配置部分
    3. 确保您的每个配置bean 都使用application.yml 中指定的config-name-here.datasource.tomcat(注意.tomcat)属性,不是没有.tomcatconfig-name-here.datasource
    4. 添加 bean 以在每个数据源前提供 DataSourceProperties 的配置
    5. 在您的 tomcat 轮询数据源上使用 @Qualifier("name of bean from previous step")


    application.yml

    # Primary Datasource
    spring:
      datasource:
        username: your-username-for-ds-1
        password: your-password-for-ds-1
        driver-class-name: net.sourceforge.jtds.jdbc.Driver
        tomcat:
          validation-query: select 1
          test-on-borrow: true
    
    
    myotherdatasource:
      datasource:
        username: your-username-for-ds-2
        password: your-password-for-ds-2
        driver-class-name: net.sourceforge.jtds.jdbc.Driver
        # HERE: make sure you have a tomcat config for your second datasource like below 
        tomcat:
          validation-query: select 1
          test-on-borrow: true
    
    


    MyCustomDatasourceConfig.java

    createPooledDataSource() 取自 Spring 项目源代码中的 DataSourceConfiguration.java

    import org.apache.tomcat.jdbc.pool.DataSource;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.jdbc.DatabaseDriver;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.beans.factory.annotation.Qualifier;
    
    
    @Configuration
    public class MyCustomDatasourceConfig {
        @Bean(name = "My-First-Data")
        @Primary
        @ConfigurationProperties(prefix = "spring.datasource.tomcat") 
        // *** NOTE the inclusion of the .tomcat above
        public DataSource primaryDataSource(DataSourceProperties properties) {
            return createPooledDataSource(properties);
        }
    
    
        @Bean()
        @Primary
        @ConfigurationProperties(prefix = "spring.datasource")
        public DataSourceProperties dataSourcePropsPrimary() {
            return new DataSourceProperties();
        }
    
    
        @Bean(name = "My-Second-Data-Source")
        @ConfigurationProperties(prefix = "myotherdatasource.datasource.tomcat") 
        // *** NOTE the inclusion of the .tomcat above
        public DataSource datasourceOtherConfig(@Qualifier("secondary_ds_prop") DataSourceProperties properties) {
            return createPooledDataSource(properties);
        }
    
        @Bean(name  = "secondary_ds_prop")
        @ConfigurationProperties(prefix = "myotherdatasource.datasource")
        public DataSourceProperties dataSourcePropsSecondary() {
            return new DataSourceProperties();
        }
    
    
        private DataSource createPooledDataSource(DataSourceProperties properties) {
            // Using fully qualified path to the tomcat datasource just to be explicit for the sake of this example
            DataSource dataSource = (org.apache.tomcat.jdbc.pool.DataSource)
                       properties.initializeDataSourceBuilder()
                       .type(org.apache.tomcat.jdbc.pool.DataSource.class).build();
            DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(properties.determineUrl());
            String validationQuery = databaseDriver.getValidationQuery();
            if (validationQuery != null) {
                dataSource.setTestOnBorrow(true);
                dataSource.setValidationQuery(validationQuery);
            }
            return dataSource;
        }
    }
    
    

    【讨论】:

      【解决方案4】:

      我已经使用了外部tomcat数据源并进行了配置。

      1) 在 /conf/server.xml 中创建了 3 个数据源

          <Resource auth="Container" driverClassName="oracle.jdbc.OracleDriver" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" maxTotal="20" maxIdle="10" maxWaitMillis="-1" name="jdbc/firstDS" password="xxxxx" type="javax.sql.DataSource" url="<url1>" username="user1"/>
      
          <Resource auth="Container" driverClassName="oracle.jdbc.OracleDriver" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" maxTotal="20" maxIdle="10" maxWaitMillis="-1" name="jdbc/secondDS" password="xxxxx" type="javax.sql.DataSource" url="<url2>" username="user2"/>
      
          <Resource auth="Container" driverClassName="oracle.jdbc.OracleDriver" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" maxTotal="20" maxIdle="10" maxWaitMillis="-1" name="jdbc/thirdDS" password="xxxxx" type="javax.sql.DataSource" url="<url3>" username="user3"/>
      

      2) 在/conf/context.xml中声明相同的数据源

          <ResourceLink auth="Container" name="jdbc/firstDS" global="jdbc/firstDS" type="javax.sql.DataSource" />
          <ResourceLink auth="Container" name="jdbc/secondDS" global="jdbc/secondDS" type="javax.sql.DataSource" />
          <ResourceLink auth="Container" name="jdbc/thirdDS" global="jdbc/thirdDS" type="javax.sql.DataSource" />
      
      

      3) 在springboot属性文件中定义数据源jndi-name

      spring.datasource.jndi-name=java:comp/env/jdbc/firstDS
      second.datasource.jndi-name=java:comp/env/jdbc/secondDS
      third.datasource.jndi-name=java:comp/env/jdbc/thirdDS
      
      

      4) 为所有 3 个数据库定义 spring boot 数据库配置,并确保将至少 1 个数据源声明为主要数据源。 firstDBConfig.java,secondDBConfig.java,thirdDBConfig.java - 只需更改 jndi-name 属性并创建 3 个配置类

      @Configuration
      @EnableTransactionManagement
      @EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory",
          transactionManagerRef = "transactionManager",   basePackages = {"com.first.application.repo"})
      public class FirstDbConfig {
      
          @Autowired
          private Environment env;
      
        @Primary
        @Bean(name = "dataSource")
        public DataSource dataSource() throws NamingException {
            return (DataSource) new JndiTemplate().lookup(env.getProperty("spring.datasource.jndi-name"));
        }
      
        @Primary
        @Bean(name = "entityManagerFactory")
        public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder, @Qualifier("dataSource") DataSource dataSource) {
          return builder.dataSource(dataSource).packages("com.first.application.domain").persistenceUnit("eamPU")
              .build();
        }
      
      
        @Primary
        @Bean(name = "transactionManager")
        public PlatformTransactionManager transactionManager(
            @Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
          return new JpaTransactionManager(entityManagerFactory);
        }
      

      5) 在定义的基础包下编写DAO和实体。所有3个Datasource类都应该放在单独的包层次结构中。

      【讨论】:

        【解决方案5】:

        上述所有解决方案都显得有些复杂。在 SpringBoot 2.0 及以上版本中,我们将 hikariCP 作为默认的连接管理库。

        只需在您的 application.yml 中定义您的数据源,如下所述,

        datasource-read:
            hikari:
              jdbc-url: someUrl
              username: someUser
              password: somePwd
              maximumPoolSize: 3
              
        datasource-write:
            hikari:
              jdbc-url: someUrl
              username: someUser
              password: somePwd
              maximumPoolSize: 3
              
        

        像这样定义你的 bean 配置,

        @Bean(name = "readDataSource")
          @ConfigurationProperties(prefix = "spring.datasource-read.hikari")
          public DataSource readDataSource() {
            return DataSourceBuilder.create().build();
          }
        
          @Primary
          @Bean(name = "readJdbcTemplate")
          public NamedParameterJdbcTemplate readJdbcTemplate(
            @Qualifier("readDataSource") DataSource readDataSource) {
            return new NamedParameterJdbcTemplate(readDataSource);
          }
        

        【讨论】:

          猜你喜欢
          • 2019-12-14
          • 2015-06-11
          • 1970-01-01
          • 2019-03-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多