4

I want to configure Spring Boot to use 2 JNDI datasources. I tried this configuration:

application.properties

spring.production.datasource.jndi-name=java:/global/production_gateway
spring.production.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.production.datasource.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
spring.production.datasource.jpa.show-sql = true
spring.production.datasource.jpa.hibernate.ddl-auto = update

spring.warehouse.datasource.jndi-name=java:/global/production_warehouse
spring.warehouse.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.warehouse.datasource.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
spring.warehouse.datasource.jpa.show-sql = true
spring.warehouse.datasource.jpa.hibernate.ddl-auto = update

primary database

    @Configuration
@EnableJpaRepositories(
        basePackages = "org.datalis.plugin.production.entity", 
        entityManagerFactoryRef = "productionEntityManagerFactory", 
        transactionManagerRef = "productionTransactionManager"
    )
@EnableTransactionManagement
public class ContextProductionDatasource {

    @Primary
    @Bean(name = "productionDataSourceProperties")
    @ConfigurationProperties(prefix="spring.production.datasource")
    public JndiPropertyHolder productionDataSourceProperties() {
        return new JndiPropertyHolder();
    }   

    @Primary
    @Bean(name = "productionDataSource")
    @ConfigurationProperties(prefix="spring.production.datasource")
    public DataSource productionDataSource() {        
        JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
        DataSource dataSource = dataSourceLookup.getDataSource(productionDataSourceProperties().getJndiName());
        return dataSource;
    }

    @Primary
    @Bean(name = "productionEntityManager") 
    public EntityManager productionEntityManager(EntityManagerFactory emf) {
        return emf.createEntityManager();
    }

    @Primary
    @Bean(name = "productionEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean productionEntityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put("hibernate.hbm2ddl.auto", "update");
        return builder
                .dataSource(productionDataSource())
                .packages("org.datalis.plugin.production.entity")
                .persistenceUnit("production")
                .properties(properties)
                .build();
    }

    @Primary
    @Bean(name = "productionTransactionManager")    
    public PlatformTransactionManager productionTransactionManager(final EntityManagerFactory emf) {
        final JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        return transactionManager;
    }

    @Primary
    @Bean(name = "productionExceptionTranslation")
    public PersistenceExceptionTranslationPostProcessor productionExceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }

    private static class JndiPropertyHolder {
        private String jndiName;

        public String getJndiName() {
            return jndiName;
        }

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

second datasource:

    @Configuration
@EnableJpaRepositories(
        basePackages = "org.datalis.plugin.warehouse.entity", 
        entityManagerFactoryRef = "warehouseEntityManagerFactory", 
        transactionManagerRef = "warehouseTransactionManager"
    )
@EnableTransactionManagement
public class ContextWarehouseDatasource {

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

    @Bean(name = "warehouseDataSource")
    @ConfigurationProperties(prefix="spring.warehouse.datasource")
    public DataSource warehouseDataSource() {
        JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
        DataSource dataSource = dataSourceLookup.getDataSource(warehouseDataSourceProperties().getJndiName());
        return dataSource;
    }

    @Bean(name = "warehouseEntityManager")  
    public EntityManager warehouseEntityManager(EntityManagerFactory emf) {
        return emf.createEntityManager();
    }

    @Bean(name = "warehouseEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean warehouseEntityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put("hibernate.hbm2ddl.auto", "update");
        return builder
                .dataSource(warehouseDataSource())
                .packages("org.datalis.plugin.warehouse.entity")
                .persistenceUnit("warehouse")
                .properties(properties)
                .build();
    }

    @Bean(name = "warehouseTransactionManager")
    public PlatformTransactionManager warehouseTransactionManager(final EntityManagerFactory emf) {
        final JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        return transactionManager;
    }

    @Bean(name = "warehouseExceptionTranslation")
    public PersistenceExceptionTranslationPostProcessor warehouseExceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }

    private static class JndiPropertyHolder {
        private String jndiName;

        public String getJndiName() {
            return jndiName;
        }

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

When I deploy the code I get exception:

    Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' available

Full error stack: https://pastebin.com/bBZPZGfu

Do you know how I can solve this issue?

When I remove:

@Primary
    @Bean(name = "productionEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean productionEntityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put("hibernate.hbm2ddl.auto", "update");
        return builder
                .dataSource(productionDataSource())
                .packages("org.datalis.plugin.production.entity")
                .persistenceUnit("production")
                .properties(properties)
                .build();
    }

The package is properly deployed. Any idea why?

Peter Penzov
  • 7,110
  • 86
  • 300
  • 595
  • The error tells you what is wrong: Failed to load driver class org.mariadb.jdbc.Driver. You need to have the mariadb driver jar in your classpath. Fix your dependencies. – JB Nizet Nov 23 '19 at 10:58
  • Please include the exception stacktrace inside the question, don't rely on external sites for this. – Mark Rotteveel Nov 24 '19 at 11:29
  • What about looking for existing answer ? https://stackoverflow.com/questions/24520602/spring-data-jpa-no-bean-named-entitymanagerfactory-is-defined-injection-of-a – Tristan Nov 25 '19 at 14:54
  • @Tristan I tries this - see my configuration files above: `@EnableJpaRepositories( basePackages = "org.datalis.plugin.production.entity", entityManagerFactoryRef = "productionEntityManagerFactory", transactionManagerRef = "productionTransactionManager"` But it's not working. – Peter Penzov Nov 25 '19 at 17:53
  • In primary database rename the method argument: EntityManager productionEntityManager(EntityManagerFactory emf) by EntityManager productionEntityManager(EntityManagerFactory productionEntityManagerFactory) And do the same for the second: warehouseEntityManager(EntityManagerFactory emf) by warehouseEntityManager(EntityManagerFactory warehouseEntityManagerFactory) – ravenskater Nov 26 '19 at 10:07
  • It's not working. – Peter Penzov Nov 26 '19 at 10:40
  • Have you tried adding @Qualifier("productionEntityManagerFactory") @Qualifier("warehouseEntityManagerFactory") for the autowired EntityManagerFactory beans in your methods? – androberz Nov 26 '19 at 13:13
  • I think you should also add a configuration property to your application.properties: `spring.data.jpa.repositories.enable=false` (this will prevent creation of repositories by default configuration, which relies on `entityManagerFactory` bean). And one more: Are you sure that you have your repository definitions in same package as entities? Your configuration states that spring data jpa should scan for repositories in `org.datalis.plugin.production.entity` and `org.datalis.plugin.warehouse.entity` packages, where your entities are specified. – Ilya Dyoshin Nov 26 '19 at 13:41
  • repository definitions and entities are in different packages – Peter Penzov Nov 26 '19 at 14:23
  • @PeterPenzov So you have to adjust your `@EnableJpaRepositories` configurations, now it states to look for repository bean definitions in entity packages. And have you disabled default JPA repositories creation (spring.data.jpa.repositories.enable=false) configuration property? – Ilya Dyoshin Nov 26 '19 at 14:34
  • In my Application start class I have this configuration: `@EntityScan(basePackages= {"org.datalis.plugin.production.entity", "org.datalis.plugin.warehouse.entity"}) @EnableJpaRepositories(basePackages= {"org.datalis.plugin.production.service", "org.datalis.plugin.warehouse.service"})` but it's not working. – Peter Penzov Nov 26 '19 at 14:36
  • You have 2 different entity managers? So you have to specify which repositories in context of which entity manager to create. Spring Data JPA is not that wise enough to determine which entity manager to use, based on which entity it wraps. – Ilya Dyoshin Nov 26 '19 at 14:41
  • I agree but for some reason it's not working. – Peter Penzov Nov 26 '19 at 14:42
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/203109/discussion-between-ilya-dyoshin-and-peter-penzov). – Ilya Dyoshin Nov 26 '19 at 14:42
  • paymentTransactionServiceImpl may have an unqualified entity manager factory auto-wired. – MaxExplode Nov 28 '19 at 14:06

3 Answers3

2

The main problem is to have 2 different Entity managers, which access different databases.

So the cause of the exception: Spring Data JPA tries to create a set of repositories but does not know which entity manager factory to use. By default, Spring Data JPA expects only one Entity manager factory bean, preferably named entityManagerFactory, but you do not have such.

So you have to be really precise in configuration: for example, you can organize your code in 2 packages: ...warehouse.* and app.production.*, then you can specify a precise configuration of Spring Data JPA: @EnableJpaRepositories(basePackages = "...warehouse.**", entityManagerFactoryRef = "warehouseEntityManagerFactory") and for production @EnableJpaRepositories(basePackages = "...production.**", entityManagerFactoryRef = "productionEntityManagerFactory").

The second step is to ensure that no default Data JPA instantiation is done: adding the configuration property spring.data.jpa.repositories.enabled=false will resolve this.

And looking through the configuration disable any kind of other @EnableJpaRepositories or @EntityScan except defined above precise configurations.

And during creation of LocalContainerEntityManagerFactoryBean do not use injected EntityManagerFactoryBuilder: dead simple new LocalContainerEntityManagerFactoryBean() will work better.

And last but not least, related to application in general: you have to think about 2-phase commit transactions: you have 2 data sources, which can be accessed within single transactions but each of them is managed by different transaction managers.

Ilya Dyoshin
  • 3,744
  • 1
  • 17
  • 16
0

Put annotation @ConfigurationProperties("spring.datasource") on you datasource definitions, e.g. warehouseDataSource() and productionDataSource()

Slav
  • 81
  • 1
  • 4
0

Eek! I'd advise following the database per microservice pattern and changing your solution/architecture to have one microservice with a production micro db, and one microservice with a warehouse micro db!

Johnny Alpha
  • 579
  • 7
  • 18