Spring Boot
Data Sources
Schema Creation
Java Development
Backend Programming

Multiple data source and schema creation 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

Using multiple data sources in Spring Boot means leaving the default one-database convention and wiring each database explicitly. The main tasks are defining separate DataSource beans, wiring the matching JPA infrastructure for each one, and deciding how schemas should be created or migrated for each database independently.

Separate the Configuration Clearly

With one database, Spring Boot autoconfiguration can do most of the work. With multiple databases, clarity matters more than convenience, so each data source should have its own configuration prefix.

Example application.yml:

yaml
1app:
2  datasource:
3    primary:
4      url: jdbc:postgresql://localhost:5432/main_db
5      username: app
6      password: secret
7      driver-class-name: org.postgresql.Driver
8    reporting:
9      url: jdbc:postgresql://localhost:5432/reporting_db
10      username: app
11      password: secret
12      driver-class-name: org.postgresql.Driver

This keeps the two connection definitions separate instead of trying to overload the default spring.datasource properties for both.

Define Two DataSource Beans

Each database needs its own DataSource. One is usually marked @Primary so Spring knows which bean to prefer when no qualifier is supplied.

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    @Primary
12    @Bean
13    @ConfigurationProperties("app.datasource.primary")
14    public DataSource primaryDataSource() {
15        return DataSourceBuilder.create().build();
16    }
17
18    @Bean
19    @ConfigurationProperties("app.datasource.reporting")
20    public DataSource reportingDataSource() {
21        return DataSourceBuilder.create().build();
22    }
23}

That gives Spring two explicit connection sources instead of one ambiguous global default.

Wire JPA Infrastructure Per Database

If both databases use JPA, each one needs its own entity manager factory and transaction manager, usually with separate entity packages.

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.context.annotation.Primary;
7import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
8import org.springframework.orm.jpa.JpaTransactionManager;
9import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
10import org.springframework.transaction.PlatformTransactionManager;
11
12@Configuration
13@EnableJpaRepositories(
14    basePackages = "com.example.primary.repo",
15    entityManagerFactoryRef = "primaryEntityManagerFactory",
16    transactionManagerRef = "primaryTransactionManager"
17)
18public class PrimaryJpaConfig {
19
20    @Primary
21    @Bean
22    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
23        EntityManagerFactoryBuilder builder,
24        @Qualifier("primaryDataSource") DataSource dataSource
25    ) {
26        return builder
27            .dataSource(dataSource)
28            .packages("com.example.primary.model")
29            .persistenceUnit("primary")
30            .build();
31    }
32
33    @Primary
34    @Bean
35    public PlatformTransactionManager primaryTransactionManager(
36        @Qualifier("primaryEntityManagerFactory") LocalContainerEntityManagerFactoryBean emf
37    ) {
38        return new JpaTransactionManager(emf.getObject());
39    }
40}

You would create a similar configuration class for the reporting database. The separation of packages is important because it prevents one entity manager from accidentally scanning the other database's entities.

Schema Creation Should Be Explicit

With multiple databases, schema creation is usually clearer when managed explicitly with Flyway or Liquibase rather than relying on one global ddl-auto setting.

Why:

  • each database may evolve independently,
  • each schema may need different migration order or ownership,
  • and automatic schema generation becomes harder to reason about once more than one entity manager is involved.

If you do use Hibernate schema generation, be deliberate about which entity manager owns which entities. Avoid one blanket global setting and assume it will do the right thing for both databases.

Common Pitfalls

  • Defining multiple DataSource beans without marking one as @Primary when the app expects a default.
  • Letting repositories or entities from one database be scanned by the wrong entity manager.
  • Relying on one global schema-generation setting for several databases with different responsibilities.
  • Mixing transactional work across databases without being explicit about which transaction manager is in use.
  • Keeping all entities in one package, which makes multi-database separation harder to understand and maintain.

Summary

  • Multiple data sources in Spring Boot require explicit configuration rather than relying on the single-database defaults.
  • Define separate DataSource beans and usually mark one as @Primary.
  • Give each JPA database its own entity manager factory, transaction manager, and repository package.
  • Keep schema creation or migrations explicit for each database instead of depending on one global assumption.
  • Clear package boundaries and bean naming are what keep multi-database Spring Boot setups manageable.

Course illustration
Course illustration

All Rights Reserved.