javapostgresqlspring-boothibernatejpa

Using Two PostgreSQL Databases in the Same Project


I'm facing the following error:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'comissionamentoEntityManagerFactory' defined in class path resource [coruripe/comissionamento/config/ComissionamentoConfig.class]: [PersistenceUnit: comissionamento] Unable to build Hibernate SessionFactory; nested exception is java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.

I am working on a project that initially had only one database for the "comissionamento" module. Now, I need to create a new separate module for "sis". I have configured it according to Spring's documentation and forums. Below are the configurations:

application.properties

# Database configurations for Comissionamento
spring.datasource.comissionamento.url=jdbc:postgresql://localhost:5432/comissionamento
spring.datasource.comissionamento.username=***
spring.datasource.comissionamento.password=***
spring.datasource.comissionamento.driver-class-name=org.postgresql.Driver

# JPA/Hibernate configurations for Comissionamento
spring.jpa.comissionamento.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.comissionamento.hibernate.ddl-auto=update
spring.jpa.comissionamento.show-sql=true
spring.jpa.comissionamento.format-sql=true

# Database configurations for SIS
spring.datasource.sis.url=jdbc:postgresql://localhost:5432/sis
spring.datasource.sis.username=***
spring.datasource.sis.password=***
spring.datasource.sis.driver-class-name=org.postgresql.Driver

# JPA/Hibernate configurations for SIS
spring.jpa.sis.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.sis.hibernate.ddl-auto=update
spring.jpa.sis.show-sql=true
spring.jpa.sis.format-sql=true
ComissionamentoConfig
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = "coruripe.comissionamento.repository",
        entityManagerFactoryRef = "comissionamentoEntityManagerFactory",
        transactionManagerRef = "comissionamentoTransactionManager"
)
public class ComissionamentoConfig {

    @Primary
    @Bean(name = "comissionamentoDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.comissionamento")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "comissionamentoEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(dataSource())
                .packages("coruripe.comissionamento.model")
                .persistenceUnit("comissionamento")
                .properties(Map.of(
                        "hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect",
                        "hibernate.hbm2ddl.auto", "update",
                        "hibernate.show_sql", "true",
                        "hibernate.format_sql", "true"
                ))
                .build();
    }

    @Primary
    @Bean(name = "comissionamentoTransactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("comissionamentoEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

SisConfig

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = "coruripe.sis.repository",
        entityManagerFactoryRef = "sisEntityManagerFactory",
        transactionManagerRef = "sisTransactionManager"
)
public class SisConfig {

    @Bean(name = "sisDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.sis")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "sisEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(dataSource())
                .packages("coruripe.sis.model")
                .persistenceUnit("sis")
                .properties(Map.of(
                        "hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect",
                        "hibernate.hbm2ddl.auto", "update",
                        "hibernate.show_sql", "true",
                        "hibernate.format_sql", "true"
                ))
                .build();
    }

    @Bean(name = "sisTransactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("sisEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

Everything seems to be configured correctly, including the part where the error occurs.

For context, here’s the project structure in case it helps.

project structure

Note: There are no entities yet in the "sis" package or any controllers. Only the directories exist at this point.


Solution

  • You should take a look at the documenation, skip the first sample but use the config as mentioned in the MyCompleteAdditionalDataSourceConfiguration which seperates out the DataSourceProperties for binding.

    @Configuration(proxyBeanMethods = false)
    public class MyCompleteAdditionalDataSourceConfiguration {
    
        @Qualifier("second")
        @Bean(defaultCandidate = false)
        @ConfigurationProperties("app.datasource")
        public DataSourceProperties secondDataSourceProperties() {
            return new DataSourceProperties();
        }
    
        @Qualifier("second")
        @Bean(defaultCandidate = false)
        @ConfigurationProperties("app.datasource.configuration")
        public HikariDataSource secondDataSource(
                @Qualifier("secondDataSourceProperties") DataSourceProperties secondDataSourceProperties) {
            return secondDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
        }
    }
    

    Instead of binding properties to the resulting datasource you first want to bind the properties to a DataSourceProperties object. Inject that specific one into a method that creates the datasource. That way it behaves in exact the same way that the default Spring Boot configuration would.

    @Configuration
    @EnableTransactionManagement
    @EnableJpaRepositories(
            basePackages = "coruripe.comissionamento.repository",
            entityManagerFactoryRef = "comissionamentoEntityManagerFactory",
            transactionManagerRef = "comissionamentoTransactionManager"
    )
    public class ComissionamentoConfig {
    
        @Primary
        @Bean(name = "comissionamentoDataSourceProperties")
        @ConfigurationProperties(prefix = "spring.datasource.comissionamento")
        public DataSourceProperties dataSourceProperties() {
          return new DataSourceProperties();
        }
    
        @Primary
        @Bean(name = "comissionamentoDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.comissionamento.properties")
        public DataSource dataSource(@Qualifier("comissionamentoDataSourceProperties) DataSourceProperties comissionamentoDataSourceProperties) {
            return comissionamentoDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
        }
    
        @Primary
        @Bean(name = "comissionamentoEntityManagerFactory")
        public LocalContainerEntityManagerFactoryBean entityManagerFactory(
                EntityManagerFactoryBuilder builder) {
            return builder
                    .dataSource(dataSource())
                    .packages("coruripe.comissionamento.model")
                    .persistenceUnit("comissionamento")
                    .properties(Map.of(
                            "hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect",
                            "hibernate.hbm2ddl.auto", "update",
                            "hibernate.show_sql", "true",
                            "hibernate.format_sql", "true"
                    ))
                    .build();
        }
    
        @Primary
        @Bean(name = "comissionamentoTransactionManager")
        public PlatformTransactionManager transactionManager(
                @Qualifier("comissionamentoEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
            return new JpaTransactionManager(entityManagerFactory);
        }
    }
    

    This will make the config work exactly as the default Spring Boot one. Now you could also use spring.datasource.comissionamento.jdbc-url as a property as that will bind directly to the underlying HikariCP datasource. However you might miss on some of the other properties you have configured which don't have a direct getter/setter on the HikariCP implementation.