Spring
Databases
Multi-database
Java Development
Spring Framework

How to use 2 or more databases with spring?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Using more than one database in Spring is mostly a wiring problem: you need separate connection beans and a clear rule for which repositories and transactions belong to which database. The difficult part is not creating two DataSource objects, but keeping the boundaries explicit so the wrong repository does not silently talk to the wrong database.

Decide What “Multiple Databases” Means

There are several different scenarios that people call multi-database support:

  • one application reads and writes two unrelated databases
  • one database is primary and another is used for audit data
  • one database is legacy and one is new
  • read and write traffic are split across separate data sources

The Spring configuration pattern is similar, but the transaction strategy may differ a lot. A local transaction manager for database A does not magically coordinate with database B.

Start with Separate Properties

A clean setup begins with separate configuration prefixes:

yaml
1app:
2  datasource:
3    primary:
4      url: jdbc:postgresql://localhost:5432/appdb
5      username: app
6      password: secret
7      driver-class-name: org.postgresql.Driver
8    audit:
9      url: jdbc:mysql://localhost:3306/auditdb
10      username: audit
11      password: secret
12      driver-class-name: com.mysql.cj.jdbc.Driver

Using distinct namespaces keeps the configuration understandable and avoids confusion with Spring Boot's default single-datasource conventions.

Define Separate DataSource Beans

Create one DataSource bean per database:

java
1import javax.sql.DataSource;
2import org.springframework.boot.context.properties.ConfigurationProperties;
3import org.springframework.boot.jdbc.DataSourceBuilder;
4import org.springframework.context.annotation.Bean;
5import org.springframework.context.annotation.Configuration;
6import org.springframework.context.annotation.Primary;
7
8@Configuration
9public class DataSourceConfig {
10
11    @Bean
12    @Primary
13    @ConfigurationProperties("app.datasource.primary")
14    public DataSource primaryDataSource() {
15        return DataSourceBuilder.create().build();
16    }
17
18    @Bean
19    @ConfigurationProperties("app.datasource.audit")
20    public DataSource auditDataSource() {
21        return DataSourceBuilder.create().build();
22    }
23}

@Primary matters because many Spring components expect one default candidate. Without it, you often run into ambiguous-bean errors.

Separate JPA Repositories by Database

If you are using Spring Data JPA, each database should usually have:

  • its own repository package
  • its own entity package
  • its own entity manager factory
  • its own transaction manager

Example for the primary database:

java
1import javax.sql.DataSource;
2import org.springframework.beans.factory.annotation.Qualifier;
3import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
4import org.springframework.context.annotation.Bean;
5import org.springframework.context.annotation.Configuration;
6import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
7import org.springframework.orm.jpa.JpaTransactionManager;
8import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
9import org.springframework.transaction.PlatformTransactionManager;
10
11@Configuration
12@EnableJpaRepositories(
13    basePackages = "com.example.primary.repo",
14    entityManagerFactoryRef = "primaryEntityManagerFactory",
15    transactionManagerRef = "primaryTransactionManager"
16)
17public class PrimaryJpaConfig {
18
19    @Bean
20    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
21            EntityManagerFactoryBuilder builder,
22            @Qualifier("primaryDataSource") DataSource dataSource) {
23        return builder
24                .dataSource(dataSource)
25                .packages("com.example.primary.entity")
26                .persistenceUnit("primary")
27                .build();
28    }
29
30    @Bean
31    public PlatformTransactionManager primaryTransactionManager(
32            @Qualifier("primaryEntityManagerFactory")
33            LocalContainerEntityManagerFactoryBean emf) {
34        return new JpaTransactionManager(emf.getObject());
35    }
36}

You repeat the pattern for the second database with different bean names and package paths.

Service Layer Usage

At the service layer, inject only the repositories that belong to the operation you are performing and specify the correct transaction manager when necessary:

java
1import org.springframework.stereotype.Service;
2import org.springframework.transaction.annotation.Transactional;
3
4@Service
5public class AccountService {
6    private final AccountRepository accountRepository;
7    private final AuditEventRepository auditEventRepository;
8
9    public AccountService(AccountRepository accountRepository,
10                          AuditEventRepository auditEventRepository) {
11        this.accountRepository = accountRepository;
12        this.auditEventRepository = auditEventRepository;
13    }
14
15    @Transactional("primaryTransactionManager")
16    public void createAccount(Account account) {
17        accountRepository.save(account);
18    }
19
20    @Transactional("auditTransactionManager")
21    public void recordEvent(AuditEvent event) {
22        auditEventRepository.save(event);
23    }
24}

Being explicit here is often better than relying on default behavior.

Think Carefully About Cross-Database Consistency

The biggest design mistake is assuming that using two datasources is only a Spring configuration issue. If one business action must update both databases atomically, that is an architectural concern.

In many applications, the better design is:

  • keep local transactions separate
  • use messaging or an outbox pattern
  • avoid pretending that two unrelated databases form one simple unit of work

Distributed transactions exist, but they add complexity and are usually not the first answer for ordinary application design.

Common Pitfalls

  • Putting repositories for both databases in the same scanned package makes ownership unclear.
  • Forgetting @Primary leads to ambiguous bean resolution.
  • Using one transaction manager everywhere can make the wrong database participate in a transaction.
  • Mixing entities from different databases in one persistence unit creates confusing runtime failures.
  • Treating cross-database consistency as just a configuration problem usually leads to fragile designs.

Summary

  • Each database should have its own DataSource and usually its own repository and transaction configuration.
  • Use separate property prefixes so the connection settings stay explicit.
  • With JPA, keep repository packages and entity packages clearly separated per database.
  • Use explicit transaction managers when a service interacts with more than one persistence layer.
  • Multi-database support is partly a Spring problem and partly an architecture problem.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.