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.
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><!-- JDBC support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
<!-- JPA & Hibernate -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<exclusions>
<exclusion>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
</exclusion>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</exclusion>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.3.7.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency></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
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.