Back to Blog

Spring Boot / Java / Database / Backend

Configuring Multiple Database Connections in Spring Boot

Learn how to configure and manage multiple database connections in Spring Boot using AbstractRoutingDataSource and Custom Bean Configuration.

|10 min read

Introduction

Modern enterprise applications often require data from multiple sources. Whether you are integrating legacy systems or implementing a micro-database pattern, Spring Boot supports multiple DataSource beans.

The Strategy

Two main approaches exist for multi-database setups:

  1. Static Configuration: Defining multiple DataSource, EntityManager, and TransactionManager beans. Best for fixed, distinct databases (e.g., Auth DB vs. Inventory DB).
  2. Dynamic Routing: Using AbstractRoutingDataSource. Best for multi-tenancy or Read/Write splitting.

When to Use Which?

Choosing between Primary and Secondary depends on your architectural pattern:

  • Read/Write Splitting: Use Primary for all @Transactional write operations (INSERT/UPDATE/DELETE) and Secondary (a read-replica) for heavy SELECT queries to reduce load.
  • Micro-database Pattern: Use Primary for your application's core domain (e.g., Users, Orders) and Secondary for auxiliary data like audit logs or analytics that reside in a different physical database.
  • Legacy Integration: Use Primary for your new modern schema and Secondary as a read-only bridge to a legacy system you are migrating away from.
  • Data Archiving: Keep "Hot" data in Primary for performance and move "Cold" historical data to Secondary (possibly a cheaper storage engine) for compliance or reporting.

Implementation Guide

1. Configuration Properties (Spring Boot 3.x)

Define your database properties in application.yml. Because we bind through DataSourceProperties and call initializeDataSourceBuilder() (shown below), the plain url property works as expected. You only need the jdbc-url key if you build the DataSource by calling DataSourceBuilder.create() directly, which bypasses DataSourceProperties and looks for jdbc-url rather than url.

1 spring:
2 datasource:
3 primary:
4 url: jdbc:postgresql://localhost:5432/primary_db
5 username: user
6 password: password
7 driver-class-name: org.postgresql.Driver
8 secondary:
9 url: jdbc:mysql://localhost:3306/secondary_db
10 username: user
11 password: password
12 driver-class-name: com.mysql.cj.jdbc.Driver

2. DataSource Configuration (Jakarta EE)

In Spring Boot 3, namespaces moved from javax.* to jakarta.*.

1 @Configuration
2 @EnableJpaRepositories(
3 basePackages = "com.example.repository.primary",
4 entityManagerFactoryRef = "primaryEntityManagerFactory",
5 transactionManagerRef = "primaryTransactionManager"
6 )
7 public class PrimaryDbConfig {
8
9 @Primary
10 @Bean
11 @ConfigurationProperties("spring.datasource.primary")
12 public DataSourceProperties primaryDataSourceProperties() {
13 return new DataSourceProperties();
14 }
15
16 @Primary
17 @Bean
18 public DataSource primaryDataSource() {
19 return primaryDataSourceProperties()
20 .initializeDataSourceBuilder()
21 .build();
22 }
23
24 // EntityManagerFactory using jakarta.persistence.EntityManager
25 }

3. Dynamic Routing with AbstractRoutingDataSource

For switching connections at runtime (e.g., based on a Header or Context):

1 public class TransactionRoutingDataSource extends AbstractRoutingDataSource {
2 @Override
3 protected Object determineCurrentLookupKey() {
4 return DbContextHolder.getDbType();
5 }
6 }

Practical Usage

Once configured, you can use @Qualifier or specific repository packages to direct your data operations.

1. Using Repositories

Spring Data JPA will automatically link repositories in com.example.repository.primary to the primary database.

1 @Service
2 @RequiredArgsConstructor
3 public class DataService {
4 private final PrimaryRepository primaryRepo;
5 private final SecondaryRepository secondaryRepo;
6
7 @Transactional("primaryTransactionManager")
8 public void syncData() {
9 var data = secondaryRepo.findAll(); // Reads from Secondary
10 primaryRepo.saveAll(data); // Writes to Primary
11 }
12 }

2. Manual DataSource Selection

If using JdbcTemplate, inject the specific bean:

1 @Service
2 public class NativeQueryService {
3 private final JdbcTemplate primaryJdbcTemplate;
4
5 public NativeQueryService(@Qualifier("primaryDataSource") DataSource dataSource) {
6 this.primaryJdbcTemplate = new JdbcTemplate(dataSource);
7 }
8 }

Conclusion

Multiple database connections add complexity to transaction management. Always ensure your @Transactional annotations specify which manager to use, e.g. @Transactional(transactionManager = "primaryTransactionManager"). Note that ChainedTransactionManager — once a common workaround here — is deprecated for removal in Spring, since it only best-effort synchronizes commits and can leave databases inconsistent on partial failure. If each database commits independently, scope a separate transaction per DataSource; if you genuinely need atomic commits across both, reach for a JTA/XA transaction manager for true two-phase commit.

Written by Erik Yuntantyo·Senior Software Engineer·About me