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

Walkthrough

Space play step click any line
Three takeaways
  1. 1A ThreadLocal lets you carry routing intent through a call stack without threading it through method signatures.
  2. 2AbstractRoutingDataSource turns one logical DataSource into many by resolving a lookup key at connection time.
  3. 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.

Read/write datasource routing in Spring — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code