Multiple DataSources in Spring Boot — Configuration and Routing
Fast recall
Quick revision
Core answer
DataSource. For multiple databases, manually define each DataSource as a @Bean, build a separate `EntityManagerFactory` and `TransactionManager` per DataSource, and use @Qualifier (or @Primary) plus @EnableJpaRepositories(entityManagerFactoryRef, transactionManagerRef, basePackages) so each repository targets the right one. `AbstractRoutingDataSource` dynamically routes a single logical DataSource to multiple physical databases based on a ThreadLocal context key — used for read/write splitting (replica vs primary) or multi-tenancy (per-tenant DB). Mixing EntityManagerFactory instances across DataSources is the most common bug — entities end up in the wrong persistence context.Whether the candidate can configure multiple databases and understands transaction manager isolation
Forgetting that each `DataSource` needs its own `EntityManagerFactory` and `TransactionManager` — mixing them causes entities to be managed by the wrong persistence context
Explain `AbstractRoutingDataSource` for read/write splitting or multi-tenancy, and discuss how `@Transactional` must specify which transaction manager to use
Interview-ready guide
Interview answer
Multiple DataSources are required when your application reads from multiple databases — primary/replica separation, microservice-owned databases, or multi-tenant schemas.
Per-DataSource configuration:
DataSourcebean (HikariCP)EntityManagerFactorybean — the JPA context for that DataSource's entity classesPlatformTransactionManagerbean@EnableJpaRepositories(basePackages, entityManagerFactoryRef, transactionManagerRef)routes repositories to the correct infrastructure
Primary bean designation:
@Primaryon oneDataSource, one EMF, one TM@Transactionalwithout qualifier uses the primary TM@Transactional("replicaTxManager")explicitly uses the secondary
`AbstractRoutingDataSource` for dynamic routing:
- Override
determineCurrentLookupKey()to read aThreadLocal - Set the key before
@Transactionalbegins via an AOP aspect - Read-only transactions route to the replica; writes route to the primary
Multi-tenancy choices:
- Database-per-tenant for strongest isolation, hundreds of tenants max
- Schema-per-tenant for logical isolation at lower cost
- Row-level with
tenant_idand Postgres RLS for cost-optimized scale-out
Multiple DataSources in Spring Boot — Configuration and Routing
Direct Answer
Spring Boot auto-configures a single DataSource. For multiple databases, manually define each DataSource as a @Bean, build a separate EntityManagerFactory and TransactionManager per DataSource, and use @Qualifier (or @Primary) plus @EnableJpaRepositories(entityManagerFactoryRef, transactionManagerRef, basePackages) so each repository targets the right one. AbstractRoutingDataSource dynamically routes a single logical DataSource to multiple physical databases based on a ThreadLocal context key — used for read/write splitting (replica vs primary) or multi-tenancy (per-tenant DB). Mixing EntityManagerFactory instances across DataSources is the most common bug — entities end up in the wrong persistence context.
Code Example
Spring Boot auto-configures one DataSource by default. For read/write splitting or multi-tenant setups, you define each DataSource manually and mark one @Primary. An AbstractRoutingDataSource then picks the active one per request.
@Configuration
public class DataSourceConfig {
@Bean @Primary @ConfigurationProperties("app.datasource.primary")
public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); }
@Bean @ConfigurationProperties("app.datasource.replica")
public DataSource replicaDataSource() { return DataSourceBuilder.create().build(); }
@Bean
public DataSource routingDataSource(@Qualifier("primaryDataSource") DataSource primary,
@Qualifier("replicaDataSource") DataSource replica) {
RoutingDataSource r = new RoutingDataSource();
r.setTargetDataSources(Map.of("PRIMARY", primary, "REPLICA", replica));
r.setDefaultTargetDataSource(primary);
return r;
}
}
public class RoutingDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> KEY = ThreadLocal.withInitial(() -> "PRIMARY");
public static void useReplica() { KEY.set("REPLICA"); }
public static void useWriter() { KEY.set("PRIMARY"); }
public static void clear() { KEY.remove(); }
@Override protected Object determineCurrentLookupKey() { return KEY.get(); }
}
@Aspect @Component
class ReadOnlyRoutingAspect {
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object route(ProceedingJoinPoint pjp) throws Throwable {
Transactional tx = ((MethodSignature) pjp.getSignature())
.getMethod().getAnnotation(Transactional.class);
if (tx != null && tx.readOnly()) RoutingDataSource.useReplica();
try { return pjp.proceed(); } finally { RoutingDataSource.clear(); }
}
}
app:
datasource:
primary:
jdbc-url: jdbc:postgresql://primary:5432/app
replica:
jdbc-url: jdbc:postgresql://replica:5432/app
read-only: true
- Each DataSource needs its own
EntityManagerFactoryandPlatformTransactionManagerwhen you use JPA — bind them with@EnableJpaRepositories(basePackages=..., entityManagerFactoryRef=..., transactionManagerRef=...). - The aspect flips the
ThreadLocalkey before the transaction starts; swapping mid-transaction is unsafe because the connection is already bound to one DataSource. - For multi-tenant apps, key by tenant ID (
RoutingDataSource.useTenant(currentTenantId())) instead of read/write — same mechanism, different lookup key.
Common Mistakes & Pitfalls
Forgetting that each DataSource needs its own EntityManagerFactory and TransactionManager — mixing them causes entities to be managed by the wrong persistence context
Check your understanding
Answer these without looking back at the article.
- How does `AbstractRoutingDataSource` interact with HikariCP — does each target need its own pool?
- What goes wrong if you set the routing `ThreadLocal` inside the `@Transactional` method?
- How do you handle Flyway migrations across many tenant schemas?
- When does outbox + CDC fail to provide acceptable consistency, forcing you to consider XA?
Related interview prep categories
2Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.