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:
- Static Configuration: Defining multiple DataSource, EntityManager, and TransactionManager beans. Best for fixed, distinct databases (e.g., Auth DB vs. Inventory DB).
- 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
@Transactionalwrite 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