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.