Skip to main content

Configuring Multiple Data Sources in a Spring Boot

Spring Database
Author
Harpal Singh
Software Engineer
Table of Contents

1. Introduction

In this quick tutorial, we’ll learn how to configure multiple data sources in a Spring Boot application.

Immagine we are working on an e-commerce app and need two separate databases to store customers and orders. We’ll configure these as separate data sources and see how to use them in our application.

2. Configuring Properties

Let’s start by defining some properties in application.properties for our Customer and Order data sources:

#Customer db
spring.datasource.customer.url = [url]
spring.datasource.customer.username = [username]
spring.datasource.customer.password = [password] 
spring.datasource.customer.driverClassName = org.postgresql.Driver

#Order db
spring.datasource.order.url = [url]
spring.datasource.order.username = [username]
spring.datasource.order.password = [password]
spring.datasource.order.driverClassName = org.sqlite.JDBC

Above, we define the data sources for the two databases and their respective properties. Moreover, we can configure different types of databases.

Now, we can use these properties inside our application.

3. Configuring Data Sources

Next, we’ll create a configuration class where we define two DataSource beans for each database:

@Configuration
public class CustomerOrderDataSourcesConfiguration {

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

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

}

We are explicitly naming our beans so we can decide which data source to refer to using the @Qualifier annotation. Additionally, we’ve marked the customerDataSource as @Primary, and we’ll consider it the main database for our application. So, in case we don’t use the @Qualifier annotation, Spring will inject the customer data source where it is required.

Let’s see how we can use these beans.

4. Using the Data Sources

We can inject the two data sources when required in our application. For example, we can use the @Autowired annotation to inject the data source inside a service class:

@Autowired  
@Qualifier("orderDataSource")
private DataSource orderDataSource;

However, most of the time, we want to hook up the data source to a Spring Data JPA repository and use that instead of interacting with the data source directly.

Let’s see how to configure that next.

5. Configuring @Repository With Specific Data Source

First, we need to create a configuration class where we use the @EnableJpaRepositories annotation to scan the repositories and define the entity manager factory for each data source:

@Configuration
@EnableJpaRepositories(
  basePackages = "com.namastecode.repositories.customer",
  entityManagerFactoryRef = "customerEntityManagerFactory", 
  transactionManagerRef = "customerTransactionManager"
)
public class CustomerDataSourceConfiguration{
  // More code
}

Now, we need to create the entityManagerFactory bean inside this class:

@Primary
@Bean(name = "customerEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean customerEntityManagerFactory(
  EntityManagerFactoryBuilder builder,
  @Qualifier("customerDataSource") DataSource dataSource) {
  return builder
    .dataSource(dataSource)
    .packages("com.namastecode.domain.customer") // entities package
    .persistenceUnit("customers")
    .build();
}

Next, we’ll create the transactionManager bean so that each data source will have its transaction manager and won’t conflict with one another:

@Primary
@Bean(name = "customerTransactionManager")
public PlatformTransactionManager customerTransactionManager(
  @Qualifier("customerEntityManagerFactory") EntityManagerFactory customerEntityManagerFactory) {
  return new JpaTransactionManager(customerEntityManagerFactory);
}

Above, we define both beans as @Primary, as the customer data source is the default one. We don’t have to do that for the orders data source.

We have everything in place to create our entities under the package com.namastecode.domain.customer:

@Entity
@Table(name = "customers")
public class Customer {
  // More code
}

Similarly, we can create the repositories under the package com.namastecode.repositories.customer:

@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
  // More code
}

Now, we have two separate data sources with their resources. However, there might be a situation where we want to configure the data sources, so we want to use a single transaction across both data sources. Let’s next see how to do that.

6. Configuring a Chained Transaction Manager

If we want to perform transactions across both data sources, we can set up a ChainedTransactionManager bean that, later, we can use when in our operations:

@Bean(name = "chainedTransactionManager")
public ChainedTransactionManager chainedTransactionManager(
  @Qualifier("customerTransactionManager") PlatformTransactionManager customerTransactionManager,
  @Qualifier("orderTransactionManager") PlatformTransactionManager orderTransactionManager) {
  return new ChainedTransactionManager(customerTransactionManager, orderTransactionManager);
}

Next, suppose we have a CustomerOrderService where we are using both CustomerRepository and OrderRepository. So, to use our chained transaction manager in this service class, we can explicitly define the transaction manager:

@Service
public class CustomerOrderService {

  @Autowired
  private CustomerRepository customerRepository;
  
  @Autowired
  private OrderRepository orderRepository;

  @Transactional(transactionManager = "chainedTransactionManager") 
  public void updateCustomerAndOrder(Customer customer, Order order) {
    customerRepository.save(customer);
    orderRepository.save(order);
  }

}

Above, if any of the save operations fails, the transaction will be rolled back for both data sources.

7. Conclusion

In this tutorial, we learned how to configure multiple data sources in a Spring Boot application.

First, we defined each data source’s properties, created the data source beans, configured the @Repository with a specific data source, and finally, saw how to configure a shared transaction manager for both data sources.

Related

Configuring a Java Web Client for HTTPS Requests
WebClient HTTPS Spring
Running PostgreSQL in Docker Container
DevOps Postgres Docker Database
Testcontainers in Spring Boot Integration Tests
Spring Testcontainers Testing