【问题标题】:Configure Multiple DataSource in Spring Boot with JNDI在 Spring Boot 中使用 JNDI 配置多个数据源
【发布时间】:2015-09-25 06:50:24
【问题描述】:

我想使用您的应用程序服务器内置功能管理 多个 数据源并使用 JNDI 访问它。我正在使用带有 Spring JPA 数据的 Spring Boot。

我能够为单个数据源配置 application.properties:

spring.datasource.jndi-name=jdbc/customers

我在 context.xml 文件中的配置如下:

<Resource name="jdbc/customer" auth="Container" type="javax.sql.DataSource"
               maxTotal="100" maxIdle="30" maxWaitMillis="10000"
               username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
               url="jdbc:mysql://localhost:3306/customer"/>

一切正常。

但是当我无法配置两个数据源时。

我确定 context.xml 文件中的配置:

 <Resource name="jdbc/customer" auth="Container" type="javax.sql.DataSource"
                   maxTotal="100" maxIdle="30" maxWaitMillis="10000"
                   username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
                   url="jdbc:mysql://localhost:3306/customer"/>

 <Resource name="jdbc/employee" auth="Container" type="javax.sql.DataSource"
                   maxTotal="100" maxIdle="30" maxWaitMillis="10000"
                   username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
                   url="jdbc:mysql://localhost:3306/employee"/>

我对 application.properties 文件的配置有疑问。

我尝试了以下选项但没有成功:

spring.datasource.jndi-name=jdbc/customers,jdbc/employee

请让我知道有关使用 JNDI 进行多数据源的 Spring boot 的任何详细信息。这几天我一直在寻找这个配置。

二审根据Spring Boot Documentation

spring.datasource.primary.jndi-name=jdbc/customer
spring.datasource.secondary.jndi-name=jdbc/project

配置类。

@Bean
@Primary
@ConfigurationProperties(prefix="datasource.primary")
public DataSource primaryDataSource() {
    return DataSourceBuilder.create().build();
}

@Bean
@ConfigurationProperties(prefix="datasource.secondary")
public DataSource secondaryDataSource() {
    return DataSourceBuilder.create().build();
}

应用程序未启动。虽然tomcat服务器正在启动。日志中不会打印任何错误。

第三次试用:使用 JndiObjectFactoryBean

我有以下 application.properties

spring.datasource.primary.expected-type=javax.sql.DataSource
spring.datasource.primary.jndi-name=jdbc/customer
spring.datasource.primary.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.datasource.primary.jpa.show-sql=false
spring.datasource.primary.jpa.hibernate.ddl-auto=validate

spring.datasource.secondary.jndi-name=jdbc/employee
spring.datasource.secondary.expected-type=javax.sql.DataSource
spring.datasource.secondary.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.datasource.secondary.jpa.show-sql=false
spring.datasource.secondary.jpa.hibernate.ddl-auto=validate

以及下面的java配置:

@Bean(destroyMethod="")
@Primary
@ConfigurationProperties(prefix="spring.datasource.primary")
public FactoryBean primaryDataSource() {
    return new JndiObjectFactoryBean();
}

@Bean(destroyMethod="")
@ConfigurationProperties(prefix="spring.datasource.secondary")
public FactoryBean secondaryDataSource() {
    return new JndiObjectFactoryBean();
}

但还是报错:

Related cause: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'primaryDataSource' defined in class path resource [com/web/initializer/MvcConfig.class]: Invocation of init method failed; nested exception is javax.naming.NameNotFoundException: Name [jdbc/customer] is not bound in this Context. Unable to find [jdbc].
Related cause: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'secondaryDataSource' defined in class path resource [com/web/initializer/MvcConfig.class]: Invocation of init method failed; nested exception is javax.naming.NameNotFoundException: Name [jdbc/employee] is not bound in this Context. Unable to find [jdbc].
        at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.onRefresh(EmbeddedWebApplicationContext.java:133)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:474)
        at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:686)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
        at org.springframework.boot.context.web.SpringBootServletInitializer.run(SpringBootServletInitializer.java:117)
        at org.springframework.boot.context.web.SpringBootServletInitializer.createRootApplicationContext(SpringBootServletInitializer.java:108)
        at org.springframework.boot.context.web.SpringBootServletInitializer.onStartup(SpringBootServletInitializer.java:68)
        at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:175)

更新: 使用以下属性文件试用:

  spring.datasource.primary.expected-type=javax.sql.DataSource
   spring.datasource.primary.jndi-name=java:comp/env/jdbc/customer

   spring.datasource.secondary.jndi-name=java:comp/env/jdbc/employee
   spring.datasource.secondary.expected-type=javax.sql.DataSource

   spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
   spring.jpa.show-sql=false
   spring.jpa.hibernate.ddl-auto=validate

它在客户模式中创建所有表,但也未能尝试找到其他表。(来自第二个模式)

【问题讨论】:

  • DataSourceBuilder 不适用于 JNDI。如果您有多个来自 JNDI 的数据源,则必须自己检索它们。

标签: java spring spring-boot spring-data jndi


【解决方案1】:

这是您第三次试用的解决方案,稍作修改。 考虑这个解决方案(Spring Boot 1.3.2):

application.properties 文件:

spring.datasource.primary.jndi-name=java:/comp/env/jdbc/SecurityDS
spring.datasource.primary.driver-class-name=org.postgresql.Driver

spring.datasource.secondary.jndi-name=java:/comp/env/jdbc/TmsDS
spring.datasource.secondary.driver-class-name=org.postgresql.Driver

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL9Dialect
spring.jpa.show-sql=false

配置:

@Configuration@ EnableConfigurationProperties
public class AppConfig {

    @Bean@ ConfigurationProperties(prefix = "spring.datasource.primary")
    public JndiPropertyHolder primary() {
        return new JndiPropertyHolder();
    }

    @Bean@ Primary
    public DataSource primaryDataSource() {
        JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
        DataSource dataSource = dataSourceLookup.getDataSource(primary().getJndiName());
        return dataSource;
    }

    @Bean@ ConfigurationProperties(prefix = "spring.datasource.secondary")
    public JndiPropertyHolder secondary() {
        return new JndiPropertyHolder();
    }

    @Bean
    public DataSource secondaryDataSource() {
        JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
        DataSource dataSource = dataSourceLookup.getDataSource(secondary().getJndiName());
        return dataSource;
    }

    private static class JndiPropertyHolder {
        private String jndiName;

        public String getJndiName() {
            return jndiName;
        }

        public void setJndiName(String jndiName) {
            this.jndiName = jndiName;
        }
    }
}

然后您可以按照指南 http://docs.spring.io/spring-data/jpa/docs/1.3.0.RELEASE/reference/html/jpa.repositories.html 将您的数据源与 jpa 存储库一起使用。

【讨论】:

    【解决方案2】:

    您可以为此使用普通的JndiObjectFactoryBean。只需将 DataSourceBuilder 替换为 JndiObjectFactoryBean 即可。

    Java 配置

    @Bean(destroyMethod="")
    @Primary
    @ConfigurationProperties(prefix="datasource.primary")
    public FactoryBean primaryDataSource() {
        return new JndiObjectFactoryBean();
    }
    
    @Bean(destroyMethod="")
    @ConfigurationProperties(prefix="datasource.secondary")
    public FactoryBean secondaryDataSource() {
        return new JndiObjectFactoryBean();
    }
    

    属性

    datasource.primary.jndi-name=jdbc/customer
    datasource.primary.expected-type=javax.sql.DataSource
    datasource.secondary.jndi-name=jdbc/project
    datasource.secondary.expected-type=javax.sql.DataSource
    

    您可以使用@ConfigurationProperties 注释设置JndiObjectFactoryBean 的每个属性。 (参见我添加的expected-type,但您也可以设置cachelookup-on-startup 等)。

    注意:在进行 JNDI 查找时,将 destroyMethod 设置为 "" 否则您可能会遇到这样的情况,即当应用程序关闭时,您的 JNDI 资源也会关闭/关闭。这不是您在共享环境中想要的。

    【讨论】:

    • 还是没有运气。 A 已将选项添加到原始问题。
    • 您是否阅读了错误信息?将resource-ref 属性设置为truedatasource.primary.resource-ref=true 或在名称前加上java:comp/env/...
    • 尝试了这两个选项(resource-ref 属性为 true 和 java:comp/env/ )。它为主要模式创建表,并为辅助模式中的表抛出错误“无法找到表”。我认为第二个数据源没有被 spring boot 自动选择?
    • 是的,但初始化仅适用于@Primary
    • 有没有办法同时使用数据源。
    【解决方案3】:

    它适用于我并且包含更少的代码

    @Configuration
    public class Config {
        @Value("${spring.datasource.primary.jndi-name}")
        private String primaryJndiName;
    
        @Value("${spring.datasource.secondary.jndi-name}")
        private String secondaryJndiName;
    
        private JndiDataSourceLookup lookup = new JndiDataSourceLookup();
    
        @Primary
        @Bean(destroyMethod = "") // destroy method is disabled for Weblogic update app ability
        public DataSource primaryDs() {
            return lookup.getDataSource(primaryJndiName);
        }
    
        @Bean(destroyMethod = "") // destroy method is disabled for Weblogic update app ability
        public DataSource secondaryDs() {
            return lookup.getDataSource(secondaryJndiName);
        }
    }
    

    【讨论】:

      【解决方案4】:

      就我而言,当我使用 Spring Boot App 启动我的应用程序时,会在 application-dev.properties 上读取数据库配置,当我使用数据源在 tomcat 上发布时,需要添加验证以检查我的配置文件是否为 prod ,在这种情况下,我做一个 jndi 查找

      @Bean(name = "dsName")
      @ConfigurationProperties("ds.datasource.configuration")
      public DataSource dataSource(@Qualifier("dsProperties") DataSourceProperties db1DataSourceProperties)
      {
      
          if(Arrays.asList(environment.getActiveProfiles()).contains("prod"))
          {
              final JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
              return dataSourceLookup.getDataSource("java:comp/env/jdbc/DS1");
          }
          else
          {
              return db1DataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
          }
      }
      

      【讨论】:

        【解决方案5】:

        我获得成功和探索更多的简洁方式

        1. 在外部tomcat中设置很多jndi资源,可以在eclipse中启动/停止。注意-在eclipse中双击tomcat,选择use workspace metadata,表示不要将app部署到tomcat webapp文件夹。在相应的 Eclipse 服务器文件中添加 jndi 资源(context.xml - ResourceLink、server.xml - Resource、web.xml - resource-ref)。

        2. 无需在 application.properties 中设置 spring.datasource.*。因为 jndi-contest 是一种数据源类型(即type="javax.sql.DataSource")被导出到外部服务器。

        3. 在 SpringBootApplication 注释类中,通过 jndi 查找从所有 jndi 资源(根据 #1 设置的那些)创建数据源 bean

          @Bean(name = "abcDataSource")
          
          public DataSource getAbcDataSource() {
          
              JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
              DataSource dataSource = dataSourceLookup.getDataSource("java:comp/env/jdbc/abcDataSource");
              return dataSource; 
          }
          
        4. 如果你的项目中使用了spring jdbc,那么提供上面的数据源来创建一个jdbcTemplate bean

          @Bean(name = "jdbcAbcTemplate")
          
          public JdbcTemplate abcJdbcTemplate(@Lazy @Qualifier("abcDataSource")
          
          DataSource refDS) 
          {        
            return new JdbcTemplate(refDS);
          }
          
        5. 只需自动装配 DataSource 类型的属性并获取系统输出其详细信息以进行更多探索。

        【讨论】:

          【解决方案6】:

          虽然上述答案很好,但如果混合 jndi 和完整数据连接配置,我将再添加一个来说明一个致命的问题。在典型的开发环境中,您可以完全限定本地开发环境中的数据库连接,然后在推送到 qa 时使用 jndi 等。您的 应用程序 .properties 看起来像这样:

          spring.datasource.url=jdbc:sqlserver://server.domain.org:1433;databaseName=dbxx
          spring.datasource.username=userxxyyzz
          spring.datasource.password=passxxyyzz
          spring.datasource.platform=mssql
          spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
          

          和 application-qa.properties 像这样:

          spring.datasource.jndi-name=java:jboss/datasources/dbxx
          

          当您必须定义自己的 bean 以拥有多个数据源时,就会出现问题。如果您使用默认的 Spring 托管数据源,那么它会自动检测 jndi 与完全限定的连接并返回一个数据源,而无需在应用程序代码中进行任何更改。如果您定义自己的数据源,它将不再这样做。如果你有这样的 application.properties:

          spring.custom.datasource.url=jdbc:sqlserver://server.domain.org:1433;databaseName=dbxx
              spring.custom.datasource.username=userxxyyzz
              spring.custom.datasource.password=passxxyyzz
              spring.custom.datasource.platform=mssql
              spring.custom.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
          

          和 application-qa.properties 像这样:

          spring.custom.datasource.jndi-name=java:jboss/datasources/dbxx
          

          使用像这样的数据源 bean,如 Spring 文档 https://docs.spring.io/spring-boot/docs/2.1.11.RELEASE/reference/html/howto-data-access.html 中所建议的那样

            @Primary
            @Bean(name="customDataSourcePropertiesBean")
            @ConfigurationProperties("spring.custom.datasource")
            public DataSourceProperties customDataSourceProperties() {
                return new DataSourceProperties();
            }
          
            @Primary
            @Bean(name="customDataSourceBean")
            @ConfigurationProperties("spring.custom.datasource") 
            public HiakriDataSource customDataSource(@Qualifier("customDataSourcePropertiesBean") DataSourceProperties properties) {
              return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
            }
          

          此数据源构建器不会尝试读取 application-qa.properties 中的 jndi 配置,而是静默故障返回到 application.properties,返回错误的数据库连接。解决方案相当简单 - 测试您所处的环境并自定义创建的数据库连接类型。调试这很麻烦,因为症状是应用程序似乎忽略了 application-qa.properties。我分担别人的痛苦。将 spring.profiles.active=qa 等添加到您的属性文件中,以了解您当时所处的环境:

            @Value("${spring.profiles.active}")
            String profile;
          
            @Value("${spring.custom.jndi-name}")
            String jndi;
          
            @Primary
            @Bean(name="customDataSourcePropertiesBean")
            @ConfigurationProperties("spring.custom.datasource")
            public DataSourceProperties customDataSourceProperties() {
                return new DataSourceProperties();
            }
          
            @Primary
            @Bean(name="customDataSourceBean")
            @ConfigurationProperties("spring.custom.datasource") 
            public DataSource customDataSource(@Qualifier("customDataSourcePropertiesBean") DataSourceProperties properties) {
              if(profile.equals("localhost")) {
                  return DataSourceBuilder
                      .create()
                          .username(properties.getDataUsername())
                          .password(properties.getPassword())
                          .url(properties.getUrl())
                          .driverClassName(properties.getDriverClassName())
                          .build();
              }else {
                  JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
                  return dataSourceLookup.getDataSource(jndi);
              }
            }
          

          【讨论】:

            猜你喜欢
            • 2020-12-14
            • 2017-06-15
            • 2018-10-13
            • 1970-01-01
            • 2015-06-11
            • 2016-08-27
            • 1970-01-01
            • 2021-03-16
            • 1970-01-01
            相关资源
            最近更新 更多