java
70 lines · 8 steps
Read/write datasource routing in Spring
How Spring's AbstractRoutingDataSource picks between primary and replica databases per request using a thread-local key.
Explained by
highlit
1public enum DataSourceType {
2 PRIMARY,
3 REPLICA
4}
5
6public final class DataSourceContextHolder {
7
8 private static final ThreadLocal<DataSourceType> CONTEXT = new ThreadLocal<>();
9
10 private DataSourceContextHolder() {
11 }
12
13 public static void set(DataSourceType type) {
14 CONTEXT.set(type);
15 }
16
17 public static DataSourceType get() {
18 return CONTEXT.get();
19 }
20
21 public static void clear() {
22 CONTEXT.remove();
23 }
24}
25
26public class RoutingDataSource extends AbstractRoutingDataSource {
27
28 @Override
29 protected Object determineCurrentLookupKey() {
30 DataSourceType type = DataSourceContextHolder.get();
31 if (type != null) {
32 return type;
33 }
34 return TransactionSynchronizationManager.isCurrentTransactionReadOnly()
35 ? DataSourceType.REPLICA
36 : DataSourceType.PRIMARY;
37 }
38}
39
40@Configuration
41public class DataSourceConfig {
42
43 @Bean
44 @ConfigurationProperties("app.datasource.primary")
45 public DataSource primaryDataSource() {
46 return DataSourceBuilder.create().build();
47 }
48
49 @Bean
50 @ConfigurationProperties("app.datasource.replica")
51 public DataSource replicaDataSource() {
52 return DataSourceBuilder.create().build();
53 }
54
55 @Bean
56 @Primary
57 public DataSource routingDataSource(
58 @Qualifier("primaryDataSource") DataSource primary,
59 @Qualifier("replicaDataSource") DataSource replica) {
60
61 RoutingDataSource routing = new RoutingDataSource();
62 Map<Object, Object> targets = new EnumMap<>(DataSourceType.class);
63 targets.put(DataSourceType.PRIMARY, primary);
64 targets.put(DataSourceType.REPLICA, replica);
65
66 routing.setTargetDataSources(targets);
67 routing.setDefaultTargetDataSource(primary);
68 return new LazyConnectionDataSourceProxy(routing);
69 }
70}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A ThreadLocal lets you carry routing intent through a call stack without threading it through method signatures.
- 2AbstractRoutingDataSource turns one logical DataSource into many by resolving a lookup key at connection time.
- 3Falling back to the transaction's read-only flag gives sensible replica routing even when no explicit hint is set.
Related explainers
typescript
import { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; import localeFrExtra from '@angular/common/locales/extra/fr'; import localeDe from '@angular/common/locales/de';
Locale-aware bootstrapping in Angular
i18n
localization
dependency-injection
Intermediate
8 steps
typescript
import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import * as Joi from 'joi';
Validating env config at boot in NestJS
configuration
schema-validation
environment-variables
Intermediate
8 steps
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; import { InjectRepository } from '@nestjs/typeorm';
A cache-aside country lookup in NestJS
cache-aside
dependency-injection
batch-lookup
Intermediate
8 steps
java
package com.acme.billing.config; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties;
Feature-flagged beans with Spring @ConditionalOnProperty
feature-flags
conditional-beans
strategy-pattern
Intermediate
5 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core'; interface Preferences { theme: 'light' | 'dark';
A signal-based preferences store in Angular
signals
state-management
persistence
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/read-write-datasource-routing-in-spring-explained-java-158f/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.