Backend Development 13 min read

Implement Dynamic Multi-DataSource Routing in Spring Boot with Annotations

This article explains why dynamic data sources are needed in high‑traffic Spring Boot applications, shows how to configure multiple databases via Maven, YAML and Java, and demonstrates a custom annotation‑driven AOP solution for seamless runtime routing.

Architecture & Thinking
Architecture & Thinking
Architecture & Thinking
Implement Dynamic Multi-DataSource Routing in Spring Boot with Annotations

1. Necessity of Dynamic Data Sources

Physical servers have limited CPU, memory, storage and connection capacity, so a surge of concurrent requests can cause database performance bottlenecks. In complex internet business scenarios, traffic grows rapidly; industry leaders therefore split large tables (horizontal or vertical sharding) and distribute them across multiple machines to improve performance. AB‑testing scenarios also require different users to see different data, which calls for dynamic data source selection.

2. Implementation Process

2.1 Maven Dependencies

<code>&lt;!-- JDBC support --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework&lt;/groupId&gt;
    &lt;artifactId&gt;spring-jdbc&lt;/artifactId&gt;
    &lt;version&gt;4.3.8.RELEASE&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
    &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
    &lt;artifactId&gt;druid-spring-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;1.1.20&lt;/version&gt;
&lt;/dependency&gt;

&lt;!-- JPA &amp; Hibernate --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
    &lt;exclusions&gt;
        &lt;exclusion&gt;
            &lt;groupId&gt;net.bytebuddy&lt;/groupId&gt;
            &lt;artifactId&gt;byte-buddy&lt;/artifactId&gt;
        &lt;/exclusion&gt;
        &lt;exclusion&gt;
            &lt;groupId&gt;org.hibernate&lt;/groupId&gt;
            &lt;artifactId&gt;hibernate-entitymanager&lt;/artifactId&gt;
        &lt;/exclusion&gt;
        &lt;exclusion&gt;
            &lt;groupId&gt;org.hibernate&lt;/groupId&gt;
            &lt;artifactId&gt;hibernate-core&lt;/artifactId&gt;
        &lt;/exclusion&gt;
    &lt;/exclusions&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
    &lt;groupId&gt;com.querydsl&lt;/groupId&gt;
    &lt;artifactId&gt;querydsl-jpa&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
    &lt;groupId&gt;com.querydsl&lt;/groupId&gt;
    &lt;artifactId&gt;querydsl-apt&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
    &lt;groupId&gt;org.hibernate&lt;/groupId&gt;
    &lt;artifactId&gt;hibernate-core&lt;/artifactId&gt;
    &lt;version&gt;5.3.7.Final&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
    &lt;groupId&gt;mysql&lt;/groupId&gt;
    &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
    &lt;scope&gt;runtime&lt;/scope&gt;
&lt;/dependency&gt;</code>

2.2 YAML Configuration

<code>spring:
  mutildata:
    basic:
      driver-class-name: com.mysql.jdbc.Driver
      filters: stat
      initial-size: 20
      logAbandoned: true
      maxActive: 300
      maxPoolPreparedStatementPerConnectionSize: 20
      maxWait: 60000
      min-idle: 5
      minEvictableIdleTimeMillis: 300000
      poolPreparedStatements: true
      removeAbandoned: true
      removeAbandonedTimeout: 1800
      testOnBorrow: false
      testOnReturn: false
      testWhileIdle: true
      timeBetweenEvictionRunsMillis: 60000
      validationQuery: SELECT 1
      password: 123456
      type: com.alibaba.druid.pool.DruidDataSource
      url: jdbc:mysql://127.0.0.1:3306/basic?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
      username: root
    mutil-data-core:
      basic:
        password: 123456
        url: jdbc:mysql://127.0.0.1:3306/basic?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
        username: root
      cloud:
        password: 123456
        url: jdbc:mysql://127.0.0.1:3307/cloudoffice?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
        username: root
      attend:
        password: 123456
        url: jdbc:mysql://127.0.0.1:3308/attend?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
        username: root</code>

2.3 Configuration Class

<code>@Bean(name = "basicDataSource")
@ConfigurationProperties(prefix = "spring.mutildata.basic")
public DruidDataSource basicDataSource() {
    return new DruidDataSource();
}

@Autowired
private DataSourceCoreConfig dataSourceCoreConfig;

@Bean(name = "routingDataSource")
@Primary
public RoutingDataSource routingDataSource(DruidDataSource basicDataSource) {
    RoutingDataSource routingDataSource = new RoutingDataSource();
    Map<Object, Object> dataSourceMap = new HashMap<>(16);
    Map<String, DataSourceCore> mutilDataCore = dataSourceCoreConfig.getMutilDataCore();
    routingDataSource.setDefaultTargetDataSource(basicDataSource);
    for (Map.Entry<String, DataSourceCore> entry : mutilDataCore.entrySet()) {
        String key = entry.getKey();
        DataSourceCore dsc = entry.getValue();
        DruidDataSource ds = (DruidDataSource) basicDataSource.clone();
        ds.setUrl(dsc.getUrl());
        ds.setUsername(dsc.getUserName());
        ds.setPassword(dsc.getPassWord());
        dataSourceMap.put(key, ds);
    }
    routingDataSource.setTargetDataSources(dataSourceMap);
    return routingDataSource;
}</code>

2.4 DataSource Collection Management

The collection stores, reads, assigns and clears data sources, ensuring each key (basic, cloud, attend) matches the YAML configuration.

2.5 Annotation Definition

<code>@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String name() default "";
}</code>

2.6 AOP Implementation

<code>@Aspect
@Component
public class DataSourceAspect implements Ordered {
    @Pointcut("@annotation(com.helenlyn.dataassist.annotation.DataSource)")
    public void dataSourcePointCut() {}

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource ds = method.getAnnotation(DataSource.class);
        String routeKey = ds.name();
        String dataSourceRouteKey = DynamicDataSourceRouteHolder.getDataSourceRouteKey();
        if (StringUtils.isNotEmpty(dataSourceRouteKey)) {
            routeKey = ds.name();
        }
        DynamicDataSourceRouteHolder.setDataSourceRouteKey(routeKey);
        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
            DynamicDataSourceRouteHolder.clearDataSourceRouteKey();
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}</code>

2.7 Test Keys and Methods

<code>public static final String DATA_SOURCE_BASIC_NAME = "basic";
public static final String DATA_SOURCE_ATTEND_NAME = "attend";
public static final String DATA_SOURCE_CLOUD_NAME = "cloud";

// Controller examples
@RequestMapping(value = "/default/{user_code}", method = RequestMethod.GET)
public UserInfoDto getUserInfo(@PathVariable("user_code") String userCode) {
    return userInfoService.getUserInfo(userCode);
}

@DataSource(name = Constant.DATA_SOURCE_ATTEND_NAME)
@RequestMapping(value = "/attend/{user_code}", method = RequestMethod.GET)
public UserInfoDto getUserInfoAttend(@PathVariable("user_code") String userCode) {
    return userInfoService.getUserInfo(userCode);
}

@DataSource(name = Constant.DATA_SOURCE_CLOUD_NAME)
@RequestMapping(value = "/cloud/{user_code}", method = RequestMethod.GET)
public UserInfoDto getUserInfoCloud(@PathVariable("user_code") String userCode) {
    return userInfoService.getUserInfo(userCode);
}</code>

2.8 Result Screenshots

3. Summary and Reference

To add a new data source, simply add its configuration under mutil-data-core in the YAML file; the routing logic will pick it up automatically. A future extension will show how to achieve the same with MyBatis.

GitHub repository: https://github.com/WengZhiHua/Helenlyn.Grocery/tree/master/parent/DynamicDataSource

JavaAOPSpring BootDynamic Data SourceAnnotationmulti-database
Architecture & Thinking
Written by

Architecture & Thinking

🍭 Frontline tech director and chief architect at top-tier companies 🥝 Years of deep experience in internet, e‑commerce, social, and finance sectors 🌾 Committed to publishing high‑quality articles covering core technologies of leading internet firms, application architecture, and AI breakthroughs.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.