web-dev-qa-db-fra.com

Configurer plusieurs DataSource dans Spring Boot avec JNDI

Je souhaite gérer plusieurs DataSource à l'aide des fonctionnalités intégrées de vos serveurs d'applications et y accéder à l'aide de JNDI. J'utilise Spring Boot avec des données Spring JPA.

Je peux configurer l'application.properties pour une seule source de données:

spring.datasource.jndi-name=jdbc/customers

Et ma configuration dans le fichier context.xml comme ci-dessous:

<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"/>

Tout fonctionne bien.

Mais quand je suis incapable de configurer deux sources de données.

Je suis sûr de la configuration dans le fichier 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"/>

Je doute de la configuration du fichier application.properties.

J'ai essayé les options ci-dessous sans succès:

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

Veuillez me faire part de tous les détails sur Spring Boot avec JNDI pour plusieurs sources de données. Je cherchais cette configuration depuis des jours maintenant.

Deuxième essai selon Spring Boot Documentation

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

Classe de configuration.

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

L'application ne démarre pas. Bien que le serveur Tomcat démarre. Aucune erreur n'est imprimée dans le journal.

Troisième essai: avec JndiObjectFactoryBean

J'ai l'application ci-dessous.

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

Et la configuration ci-dessous 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();
}

Mais toujours une erreur:

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)

Mise à jour: Essai en utilisant le fichier de propriétés ci-dessous:

  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

Il crée toutes les tables dans le schéma client, mais ne parvient pas à trouver les autres tables également. (À partir du deuxième schéma)

9
Manu

C'est la solution de votre troisième essai un peu modifiée. Considérez cette solution (Spring Boot 1.3.2):

fichier 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:

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

Et puis vous pouvez suivre le guide http://docs.spring.io/spring-data/jpa/docs/1.3.0.RELEASE/reference/html/jpa.repositories.html pour utiliser vos sources de données avec les référentiels jpa.

7
Andrey Sarul

Vous pouvez utiliser un simple JndiObjectFactoryBean pour cela. Remplacez simplement le DataSourceBuilder par un JndiObjectFactoryBean devrait faire l'affaire.

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

Propriétés

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

Vous pouvez définir toutes les propriétés de JndiObjectFactoryBean à l'aide de @ConfigurationProperties annotation. (Voir le expected-type J'ai ajouté, mais vous pouvez également définir cache ou lookup-on-startup etc.).

Remarque: lorsque vous effectuez une recherche JNDI, définissez destroyMethod sur "" sinon, vous pourriez avoir la situation selon laquelle, lorsque l'application est à l'arrêt, votre ressource JNDI se ferme/s'arrête également. Ce n'est pas quelque chose que vous voulez dans un environnement partagé.

5
M. Deinum

Cela fonctionne pour moi et contient moins de code

@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
Andriy Slobodyanyk

La façon concise d'obtenir du succès et d'explorer plus

  1. configurer de nombreuses ressources jndi dans Tomcat externe, que vous pouvez démarrer/arrêter dans Eclipse. Ajoutez des ressources jndi dans les fichiers de serveur Eclipse respectifs (context.xml - ResourceLink, server.xml - Resource, web.xml - resource-ref).

  2. pas besoin de définir spring.datasource. * dans application.properties. depuis jndi-contest qui est un type de source de données (c'est-à-dire type="javax.sql.DataSource") est exporté vers un serveur externe.

  3. dans la classe annotée SpringBootApplication, créez les beans source de données à partir de toutes les ressources jndi (celles configurées selon # 1) via la recherche jndi

    @Bean(name = "abcDataSource")
    
    public DataSource getAbcDataSource() {
    
        JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
        DataSource dataSource = dataSourceLookup.getDataSource("Java:comp/env/jdbc/abcDataSource");
        return dataSource; 
    }
    
  4. si spring jdbc utilisé dans votre projet, fournissez la source de données ci-dessus pour créer un bean jdbcTemplate

    @Bean(name = "jdbcAbcTemplate")
    
    public JdbcTemplate abcJdbcTemplate(@Lazy @Qualifier("abcDataSource")
    
    DataSource refDS) 
    {        
      return new JdbcTemplate(refDS);
    }
    
  5. il suffit de câbler automatiquement une propriété de type DataSource et d'obtenir les détails du système pour en savoir plus.

0
subrat22

Bien que les réponses ci-dessus soient bonnes, je vais en ajouter une de plus pour illustrer un piège mortel si vous mélangez jndi et une configuration de connexion de données complète. Dans un environnement de développement typique, vous pouvez pleinement qualifier la connexion à la base de données dans votre environnement de développement local, puis utiliser jndi lorsque vous passez à qa, etc. Votre application .properties ressemble à ceci:

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

et application-qa.properties comme ceci:

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

Le problème se pose lorsque vous devez définir vos propres beans pour avoir plusieurs sources de données. Si vous utilisez la source de données gérée par défaut par défaut, il détecte automatiquement jndi par rapport à une connexion pleinement qualifiée et renvoie une source de données sans modification nécessaire dans le code d'application. Si vous définissez votre propre source de données, cela ne se fait plus. Si vous avez des applications.properties comme ceci:

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

et application-qa.properties comme ceci:

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

avec un bean de source de données comme celui-ci, comme suggéré dans Spring docs 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();
  }

Ce générateur de source de données n'essaie pas de lire la configuration jndi dans application-qa.properties et échoue en silence à application.properties renvoyant la connexion de base de données WRONG. La résolution est assez simple - testez l'environnement dans lequel vous vous trouvez et personnalisez le type de connexion à la base de données créée. Le débogage était un ours, car le symptôme était que l'application semblait ignorer application-qa.properties. Je partage pour épargner aux autres la douleur. Ajoutez spring.profiles.active = qa etc. à vos fichiers de propriétés pour savoir dans quel environnement vous vous trouvez alors:

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