Spring Boot
multiple databases
database connectivity
Java
application development

Connecting to multiple database in spring boot

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Spring Boot can connect to multiple databases, but only if each persistence module is configured explicitly. Most issues come from ambiguous bean wiring or repository packages that accidentally point to the wrong datasource. A maintainable setup defines separate properties, datasources, entity managers, and transaction managers for each database.

Define Separate Configuration Properties

Do not overload one default datasource block. Use dedicated prefixes per database.

properties
1app.datasource.main.url=jdbc:postgresql://localhost:5432/main_db
2app.datasource.main.username=app
3app.datasource.main.password=secret
4
5app.datasource.audit.url=jdbc:postgresql://localhost:5432/audit_db
6app.datasource.audit.username=app
7app.datasource.audit.password=secret

Clear prefixes make wiring intent explicit and easier to debug.

Create Distinct DataSource Beans

Bind each property group to a separate datasource bean.

java
1import javax.sql.DataSource;
2import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
3import org.springframework.boot.context.properties.ConfigurationProperties;
4import org.springframework.context.annotation.Bean;
5import org.springframework.context.annotation.Configuration;
6
7@Configuration
8public class DataSourceConfig {
9
10    @Bean
11    @ConfigurationProperties("app.datasource.main")
12    public DataSourceProperties mainProps() {
13        return new DataSourceProperties();
14    }
15
16    @Bean
17    public DataSource mainDataSource() {
18        return mainProps().initializeDataSourceBuilder().build();
19    }
20
21    @Bean
22    @ConfigurationProperties("app.datasource.audit")
23    public DataSourceProperties auditProps() {
24        return new DataSourceProperties();
25    }
26
27    @Bean
28    public DataSource auditDataSource() {
29        return auditProps().initializeDataSourceBuilder().build();
30    }
31}

Use explicit bean names and qualifiers to avoid ambiguous injection.

Split JPA Configuration by Module

Each datasource should have its own entity manager and transaction manager, then repositories must be bound to the correct pair.

java
1import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
2
3@EnableJpaRepositories(
4    basePackages = "com.example.main.repo",
5    entityManagerFactoryRef = "mainEntityManagerFactory",
6    transactionManagerRef = "mainTransactionManager"
7)
8public class MainJpaConfig {}

Create a parallel config for audit packages with distinct refs.

Use Explicit Transaction Managers in Services

When methods target one database, annotate with the matching transaction manager name.

java
1import org.springframework.transaction.annotation.Transactional;
2
3public class OrderService {
4
5    @Transactional("mainTransactionManager")
6    public void createOrder() {
7        // write main database
8    }
9
10    @Transactional("auditTransactionManager")
11    public void writeAudit() {
12        // write audit database
13    }
14}

This prevents accidental writes to wrong datasource under default transaction manager.

Keep Package Boundaries Strict

Organize entities and repositories by database ownership:

  • com.example.main.entity and com.example.main.repo
  • com.example.audit.entity and com.example.audit.repo

Loose package boundaries are a common cause of cross-database repository wiring bugs.

Test Wiring with Realistic Integration Tests

Unit tests are not enough for multi-database wiring. Add integration tests that verify each repository writes to intended database.

A practical approach is running two test databases with Testcontainers and checking row presence separately after service calls.

This catches misconfiguration before deployment.

Observe Datasource Health Separately

Expose and monitor health and pool metrics per datasource so incidents are isolated quickly.

properties
management.endpoints.web.exposure.include=health,metrics

If one database slows down, dashboards should show which pool is saturated and which remains healthy.

Migration and Ownership Discipline

Keep migration scripts separate per datasource. Each schema should have independent migration history and ownership.

During release, apply migrations with datasource-specific config and log target datasource names. This avoids accidental schema changes in wrong database.

Consider Read and Write Routing Explicitly

Some systems use one datasource for writes and another for read replicas. Keep routing logic explicit in service design and never hide it behind ambiguous defaults.

java
1@Transactional("mainTransactionManager")
2public Order saveOrder(Order order) {
3    return mainOrderRepository.save(order);
4}

If read replicas are eventually consistent, document freshness expectations so callers do not assume immediate read-after-write behavior.

Common Pitfalls

  • Using one default datasource config in multi-database applications.
  • Letting repositories scan overlapping packages.
  • Omitting transaction manager names in multi-database service methods.
  • Injecting datasource beans without qualifiers.
  • Skipping integration tests that validate actual datasource routing.

Summary

  • Multi-database Boot setup requires explicit, separate wiring for each datasource.
  • Define dedicated properties, datasources, entity managers, and transaction managers.
  • Keep repository packages strictly separated by database ownership.
  • Use named transaction managers in service methods.
  • Add integration and observability checks to keep routing correct in production.

Course illustration
Course illustration

All Rights Reserved.