使用JNDI在Spring Boot中配置多个数据源

14

我希望能够利用您应用服务器内置的功能,使用JNDI访问并管理多个数据源。我正在使用Spring boot与Spring JPA数据。

我已经成功地为单个数据源配置了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文档中的第二个示例尝试: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 的数据源,您必须自己检索它们。 - M. Deinum
6个回答

10

这是您第三次试验的解决方案,稍作修改。 考虑使用此解决方案(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存储库来使用你的数据源。


6
你可以使用普通的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资源也会被关闭/关闭。这不是您在共享环境中想要的。

仍然没有运气。我已经在原问题中添加了选项。 - Manu
你读过错误信息吗?要么将“resource-ref”属性设置为true(datasource.primary.resource-ref=true),要么在名称前加上“java:comp/env/”... - M. Deinum
尝试了两种选项(将resource-ref属性设置为true和使用java:comp/env/),它可以为主模式创建表格,但对于次要模式中的表格会抛出错误“无法找到表格”。我认为Spring Boot没有自动选择第二个数据源? - Manu
是的,但初始化只适用于“@Primary”一个。 - M. Deinum
是的,我理解了。我试图使用通过JNDI配置的两个数据源,使用Spring data JPA。我已成功地使用单个数据源。任何帮助都将不胜感激!! - Manu
显示剩余4条评论

6

它适合我并且代码量更少

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

1
在我的情况下,当我使用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();
    }
}

0

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

  1. 在外部Tomcat中设置许多JNDI资源,这样您可以在Eclipse中启动/停止它们。注意 - 在Eclipse中双击Tomcat并选择使用工作区元数据,意味着不将应用程序部署到Tomcat webapp文件夹中。在各自的Eclipse服务器文件(context.xml-ResourceLink,server.xml-Resource,web.xml-resource-ref)中添加JNDI资源。

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

  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类型的属性并获取其详细信息以了解更多内容。


0
虽然上面的回答很好,但我要再加一个来说明一个致命的陷阱,如果混合使用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);
    }
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接