如何在Spring Boot中使用多个数据源时设置多个连接池?

时间:2017-08-25 07:34:08

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

我有一个连接到两个独立数据库的Spring Boot应用程序。一切正常(我遵循the docsa tutorial中的步骤),但为了自定义Tomcat JDBC连接池设置,I had to manually configure it(因为通过定义多个数据源,引导自动-configuration被忽略,Spring Boot不再从application.properties读取特定于tomcat的属性。

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

但是,从我看到的使用jConsole + tomcat JMX,只有一个连接池,它配置了主数据库详细信息(URL,凭据,见下文)。 jConsole inspecting the Tomcat JDBC JMX info about the Connection Pool

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

最重要的是,你能帮助我理解:

  1. 为什么只有一个连接池
  2. 如何使用两个连接池,每个数据源一个
  3. 在Spring代码中查看有关其工作原理的更多详细信息
  4. 对于第二个问题,有一点related question,但没有答案。有another question这是误报,another one与Spring有关,而不是Spring Boot,所以请不要将其作为dupe报告。

5 个答案:

答案 0 :(得分:2)

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

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

    如果需要HikariDbcp2,请使用原始Spring源文件中的createPooledDataSource()Hikari配置节替换下面方法Dbcp2的内容DataSourceConfiguration.java。下方createPooledDataSource()的显示内容是从链接文件中的Tomcat.dataSource()方法窃取的。

  2. 在您的tomcat中的datasource配置的每个下添加application.yml配置部分
  3. 确保每个配置bean都使用config-name-here.datasource.tomcat中指定的.tomcat(请注意application.yml)属性,而不是{{1} },不带config-name-here.datasource
  4. 添加bean为每个数据源提供.tomcat的配置
  5. 在您的tomcat轮询数据源上使用DataSourceProperties


application.yml

@Qualifier("name of bean from previous step")


MyCustomDatasourceConfig.java <-您的自定义配置文件

  

# 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 取自Spring项目源中的DataSourceConfiguration.java

createPooledDataSource()

答案 1 :(得分:1)

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

答案 2 :(得分:0)

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

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

<ion-col

在上面的示例中,testDatabaseConnection()调用Spring Data Repository上的方法

@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());
    }
}

答案 3 :(得分:0)

我已经使用了外部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 DB配置,并确保将至少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类都应放在单独的包层次结构中。

答案 4 :(得分:0)

以上所有解决方案似乎有点复杂。在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);
  }