How to connect multiple databases in Spring Boot

Viewed 1854

I want to develop an application to connect to multiple oracle databases and fetch data from same table from each database.

I have tried using the multiple datasource for every database and fetch the data from the tables.

Resource: https://medium.com/@joeclever/using-multiple-datasources-with-spring-boot-and-spring-data-6430b00c02e7

DB1 Config:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "CU1EntityManagerFactory", basePackages = {
      "com.javatechie.multiple.ds.api.cu1.repository" })
public class CU1Config {
   @Primary
   @Bean(name = "CU1DataSource")
   @ConfigurationProperties(prefix = "spring.cu1.datasource")
   public DataSource dataSource() {
      return DataSourceBuilder.create().build();
   }

   @Primary
   @Bean(name = "CU1EntityManagerFactory")
   public LocalContainerEntityManagerFactoryBean CU1EntityManagerFactory(EntityManagerFactoryBuilder builder,
                                                        @Qualifier("CU1DataSource") DataSource dataSource) {
      HashMap<String, Object> properties = new HashMap<>();
      properties.put("hibernate.hbm2ddl.auto", "update");
      properties.put("hibernate.dialect", "org.hibernate.dialect.Oracle12cDialect");
      return builder.dataSource(dataSource).properties(properties)
            .packages("com.javatechie.multiple.ds.api.model").persistenceUnit("Unit").build();
   }

}

DB2 Config:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "CU2EntityManagerFactory", basePackages = {
      "com.javatechie.multiple.ds.api.cu2.repository" })
public class CU2Config {

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

   @Bean(name = "CU2EntityManagerFactory")
   public LocalContainerEntityManagerFactoryBean CU2EntityManagerFactory(EntityManagerFactoryBuilder builder,
                                                        @Qualifier("CU2DataSource") DataSource dataSource) {
      HashMap<String, Object> properties = new HashMap<>();
      properties.put("hibernate.hbm2ddl.auto", "update");
      properties.put("hibernate.dialect", "org.hibernate.dialect.Oracle12cDialect");
      return builder.dataSource(dataSource).properties(properties)
            .packages("com.javatechie.multiple.ds.api.model").persistenceUnit("Unit").build();
   }

}

But the issue that I am facing is for every datasource I have to create a separate config class and also a dedicated repository and service, which is code repeatability though the work from each DB is same to fetch data from a particular table.

And also in future if I want to add any new database I have to repeat the same above process again.

Any help regarding better approach for code reusability is highly appreciated.

2 Answers

You can do it directly through the properties file

spring.datasource.jdbcUrl = [url]
spring.datasource.username = [username]
spring.datasource.password = [password]
spring.second-datasource.jdbcUrl = [url]
spring.second-datasource.username = [username]
spring.second-datasource.password = [password]

Then you need to create the configurations for the datasources:

@Configuration
@PropertySource({"classpath:persistence-multiple-db-boot.properties"})
@EnableJpaRepositories(
  basePackages = "com.baeldung.multipledb.dao.user",
  entityManagerFactoryRef = "userEntityManager",
  transactionManagerRef = "userTransactionManager")
public class PersistenceUserAutoConfiguration {
    
    @Primary
    @Bean
    @ConfigurationProperties(prefix="spring.datasource")
    public DataSource userDataSource() {
        return DataSourceBuilder.create().build();
    }
    // userEntityManager bean 

    // userTransactionManager bean
}

The config for the second one:

@Configuration
@PropertySource({"classpath:persistence-multiple-db-boot.properties"})
@EnableJpaRepositories(
  basePackages = "com.baeldung.multipledb.dao.product", 
  entityManagerFactoryRef = "productEntityManager", 
  transactionManagerRef = "productTransactionManager")
public class PersistenceProductAutoConfiguration {
   
    @Bean
    @ConfigurationProperties(prefix="spring.second-datasource")
    public DataSource productDataSource() {
        return DataSourceBuilder.create().build();
    }
   
    // productEntityManager bean 

    // productTransactionManager bean
}

You can find more about this implementation here

If you are going to create a serious service, you should know only how to config them, how to use them, and how to control transaction for all of databases via Spring.

You can see the runnable example and some explanation in https://github.com/surasint/surasint-examples/tree/master/spring-boot-jdbi/10_spring-boot-two-databases (see what you can try in README.txt)

I copied some code here.

First you have to set application.properties like this

#Database
database1.datasource.url=jdbc:mysql://localhost/testdb
database1.datasource.username=root
database1.datasource.password=root
database1.datasource.driver-class-name=com.mysql.jdbc.Driver

database2.datasource.url=jdbc:mysql://localhost/testdb2
database2.datasource.username=root
database2.datasource.password=root
database2.datasource.driver-class-name=com.mysql.jdbc.Driver

Then define them as providers (@Bean) like this:

@Bean(name = "datasource1")
@ConfigurationProperties("database1.datasource")
@Primary
public DataSource dataSource(){
    return DataSourceBuilder.create().build();
}

@Bean(name = "datasource2")
@ConfigurationProperties("database2.datasource")
public DataSource dataSource2(){
    return DataSourceBuilder.create().build();
}

Note that I have @Bean(name="datasource1") and @Bean(name="datasource2"), then you can use it when we need datasource as @Qualifier("datasource1") and @Qualifier("datasource2") , for example

@Qualifier("datasource1")
@Autowired
private DataSource dataSource;

If you do care about transaction, you have to define DataSourceTransactionManager for both of them, like this:

@Bean(name="tm1")
@Autowired
@Primary
DataSourceTransactionManager tm1(@Qualifier ("datasource1") DataSource datasource) {
    DataSourceTransactionManager txm  = new DataSourceTransactionManager(datasource);
    return txm;
}

@Bean(name="tm2")
@Autowired
DataSourceTransactionManager tm2(@Qualifier ("datasource2") DataSource datasource) {
    DataSourceTransactionManager txm  = new DataSourceTransactionManager(datasource);
    return txm;
}

Then you can use it like

@Transactional //this will use the first datasource because it is @primary

or

@Transactional("tm2")

The most important part, which you will hardly find an example in anywhere: if you want a method to commit/rollback transactions of both databases, you need ChainedTransactionManager for tm1 and tm2 , like this:

@Bean(name = "chainedTransactionManager")
public ChainedTransactionManager getChainedTransactionManager(@Qualifier ("tm1") DataSourceTransactionManager tm1, @Qualifier ("tm2") DataSourceTransactionManager tm2){
    return new ChainedTransactionManager(tm1, tm2);
}

To use it, add this annotation in a method @Transactional(value="chainedTransactionManager") for example

@Transactional(value="chainedTransactionManager")
public void insertAll() {
    UserBean test = new UserBean();
    test.setUsername("username" + new Date().getTime());
    userDao.insert(test);

    userDao2.insert(test);
}

This should be enough. See example and detail in the link above.

Related