Multi-datasource routing for Spring Boot — switch with a single annotation.
Read/write splitting · one master / many slaves · polyglot databases · dynamic routing · distributed & local transactions
English · 简体中文
- Why this project
- Highlights
- Compatibility
- Quick start
- Naming conventions & routing rules
- Transactions
- Dynamic datasource keys
- Runtime management
- Ecosystem integrations
- Module layout
- Documentation
- Contributing, Security and License
Adding a second database to a Spring Boot application usually means hand-writing a second
DataSource bean, a second TransactionManager, a second set of MyBatis/JPA wiring — and then
still not knowing how to route a call to one of them.
dynamic-datasource replaces all of that with a single starter: declare your datasources in
YAML, annotate a class or method with @DS("slave"), and the framework resolves the connection at
invocation time, restores it when the call returns, and nests correctly across service layers. It
has been maintained since 2018, ships artifacts for three Spring Boot generations, and is validated
by CI on a JDK 17/21/25/27 matrix (the Boot 2 line is compiled and tested through a JDK 8 toolchain)
plus GraalVM Native Image.
It does exactly one thing — choosing which
DataSourcea call runs against — and deliberately does not constrain what you do afterwards. Any CRUD, any ORM, any transaction strategy.
The full reference — every configuration key, connection pool and integration recipe — lives on doc.xiuceyun.cn (Chinese); the wiki is taking over page by page. See Documentation.
| Area | What you get |
|---|---|
| Grouping | Pure multi-DB, read/write splitting, one-master-many-slaves, or a mix of all three, expressed by naming alone. |
| Routing | @DS on method / class / interface / abstract class, nearest declaration wins; group names resolve through a load-balanced or random strategy. |
| Transactions | Local multi-datasource transactions via @DSTransactional (7 propagation levels), plus Seata AT / XA and Atomikos XA for truly distributed commits. |
| Security | ENC(...) encrypted credentials out of the box, customisable algorithm and public key; SpEL type access restricted by default. |
| Lifecycle | Start with zero datasources, lazy-initialise on first use, add/remove datasources at runtime, graceful shutdown that waits for in-flight connections. |
| Extensibility | Custom annotations that meta-annotate @DS, custom DsProcessor chain, custom DataSourceProvider, pluggable DataSourceCreator. |
| Connectivity | Curated integration for Druid, HikariCP, BeeCP, DBCP2, C3P0, Oracle UCP, unpooled basic datasources and JNDI lookups. |
| Native | GraalVM reachability metadata published as an artifact; nativeTest gates every commit in CI. |
Pick the starter that matches your Spring Boot line (the maintenance status of each line is defined in SUPPORT.md):
| Spring Boot | JDK | Starter artifact | Available since |
|---|---|---|---|
| 1.5.x ~ 2.x.x | JDK 8+ | dynamic-datasource-spring-boot-starter |
1.0.0 |
| 3.x.x | JDK 17+ | dynamic-datasource-spring-boot3-starter |
4.0.0 |
| 4.x.x | JDK 17+ | dynamic-datasource-spring-boot4-starter |
4.5.0 |
Connection pools (Druid / HikariCP / …) and JDBC drivers are not bundled. Bring your own and keep the versions aligned with your Spring Boot line — this is what keeps the starter lightweight and avoids the classpath conflicts that a shaded starter would cause.
1 · Add the dependency (shown for 4.5.0; replace with the latest release).
Maven
<!-- Spring Boot 2.x (and 1.5.x) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
<!-- Spring Boot 3.x -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>4.5.0</version>
</dependency>
<!-- Spring Boot 4.x -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot4-starter</artifactId>
<version>4.5.0</version>
</dependency>Gradle
// choose exactly one, matching your Spring Boot line
implementation 'com.baomidou:dynamic-datasource-spring-boot-starter:4.5.0'
implementation 'com.baomidou:dynamic-datasource-spring-boot3-starter:4.5.0'
implementation 'com.baomidou:dynamic-datasource-spring-boot4-starter:4.5.0'2 · Declare your datasources. Everything after the datasource: key is a datasource name; the
part before the first _ becomes its group.
spring:
datasource:
dynamic:
enabled: true # master switch, default true
primary: master # default datasource or group, default "master"
strict: false # true = throw when the key does not resolve
grace-destroy: false # true = wait up to 10s for active connections on shutdown
datasource:
master:
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver # optional since 3.2.0 (SPI based)
slave_1:
url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave_2:
url: ENC(xxxxx) # built-in encryption, see the documentation
username: ENC(xxxxx)
password: ENC(xxxxx)
driver-class-name: com.mysql.cj.jdbc.DriverThe snippet above yields one master datasource plus a slave group containing slave_1 and
slave_2. Other shapes are equally declarative:
# one master, many slaves # pure multi-database # mixed
master: mysql: master:
slave_1: oracle: slave_1:
slave_2: sqlserver: slave_2:
h2: oracle_1:
oracle_2:3 · Route with @DS. Applicable to methods and types; when both are present the method wins.
@Service
@DS("slave")
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@DS("master") // pinned to the master datasource
public List<Map<String, Object>> selectAll() {
return jdbcTemplate.queryForList("select * from user");
}
@DS("slave_1") // pinned to one concrete datasource
public List<Map<String, Object>> selectByCondition() {
return jdbcTemplate.queryForList("select * from user where age > 10");
}
}| Annotation | Resolved datasource |
|---|---|
| none | the primary datasource |
@DS("slave") |
the slave group, balanced across its members |
@DS("slave_1") |
the slave_1 datasource |
@Master / @Slave |
shortcuts shipped for the master / slave names |
Any annotation that is itself meta-annotated with @DS works as a datasource switch — that is exactly
how @Master and @Slave are shipped, and it lets you define a domain-specific one such as
@OracleDs declared as @DS("oracle").
- All datasource configuration keys are read under
spring.datasource.dynamic.datasource. - The segment before the first
_is the group name; names sharing a group are pooled into oneGroupDataSource. - You may switch to a group name or to a concrete datasource name; groups are resolved by
strategy(load balance by default,RandomDynamicDataSourceStrategyoptional). - The default datasource is
master, overridable viaspring.datasource.dynamic.primary. - Resolution priority, nearest wins: programmatic push (
DynamicDataSourceContextHolder.push) > method annotation > class annotation, and@DSinherited from interfaces or abstract classes is honoured. - The datasource stack is pushed and popped per invocation, so
ServiceA >>> ServiceB >>> ServiceCnesting across different datasources behaves as expected.
Local, no external coordinator — @DSTransactional binds one connection per participating
datasource under a transaction id, then commits or rolls all of them back together at the boundary;
nested transactions are handled through save points. A failure in the last datasource undoes what the
earlier ones already wrote.
@DSTransactional(propagation = DsPropagation.REQUIRED, rollbackFor = Exception.class)
@DS("master")
public void transfer() {
userMapper.debit(); // master
logMapper.write(); // log_1, switched inside the call chain via @DS
}Propagation levels: REQUIRED (default), REQUIRES_NEW, NESTED, SUPPORTS, NOT_SUPPORTED, MANDATORY, NEVER.
Transaction events can be observed with @DsTxEventListener (commit / rollback / after-completion), the
multi-datasource counterpart of Spring's @TransactionalEventListener.
Distributed — two first-class options:
# 1) Seata: proxy each datasource for a global commit
spring:
datasource:
dynamic:
seata: true # wrap datasources for Seata
seata-mode: AT # AT (default) or XA# 2) Atomikos XA: point driver-class-name at an XA datasource
spring:
datasource:
dynamic:
atomikos: # global pool tuning; per-datasource overrides apply on top
max-pool-size: 10
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.MysqlXADataSource
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamicSupported XA resources: MySQL (5.x and 8+), Oracle, PostgreSQL, H2; on the MyBatis side pair them with
AtomikosTransactionFactory.
The @DS value does not have to be a literal. The built-in DsProcessor chain resolves it per
request, and you can prepend your own processor:
@DS("#header.ds") // read the datasource key from an HTTP header
@DS("#session.dsName") // ... or from the HTTP session
@DS("#user.level") // SpEL against the method arguments
@DS("@dsLocator.for(#id)") // SpEL with a bean referenceSpEL type references (
T(java.lang.Runtime)) and constructor expressions are disabled by default to block expression injection. Re-enable them only when genuinely required, viaspring.datasource.dynamic.aop.allowed-spel-type-access: true.
Related AOP knobs: aop.enabled, aop.order (default HIGHEST_PRECEDENCE), aop.allowed-public-only.
@Autowired
private DynamicRoutingDataSource routingDataSource;
@Autowired
private DefaultDataSourceCreator dataSourceCreator;
public void openTenant(String tenantId) {
DataSourceProperty property = new DataSourceProperty();
property.setPoolName("tenant_" + tenantId);
property.setUrl("jdbc:mysql://db.internal:3306/tenant_" + tenantId);
property.setUsername("app");
property.setPassword("secret");
// the property goes through the creator chain and becomes a real, initialised pool
routingDataSource.addDataSource(property.getPoolName(), dataSourceCreator.createDataSource(property));
}
public void closeTenant(String tenantId) {
routingDataSource.removeDataSource("tenant_" + tenantId); // destroyed, gracefully if configured
}lazy: true— a datasource is created on first access instead of at startup.- Starting with no datasource is supported;
AbstractJdbcDataSourceProviderlets you load the catalogue itself from a bootstrap database. - Per-datasource initialisation scripts (
init.schema/init.data), each with its own separator and error policy, so a group of heterogeneous databases can bootstrap independently. grace-destroy: true— on shutdown, wait (at most 10s) for active connections before forcing close.ENC(...)credentials with a global or per-datasourcepublic-key, andEncDataSourceInitEventas the hook for your own algorithm (AES, SM4, KMS…).p6spy: truewraps datasources for SQL logging;seata: falseopts a single datasource out of proxying.
- MyBatis / MyBatis-Plus — usable as a plain
DataSource;MasterSlaveAutoRoutingPlugingives pure read/write splitting with no annotation at all (SELECT→slave, everything else →master). - Quartz — hand the scheduler the dynamic
DataSource; the documented recipe covers the JDBC job store. - ShardingSphere / ShardingJDBC, P6spy, JNDI — integration recipes are on the documentation site; they are being migrated into the wiki step by step.
- GraalVM Native Image — reachability metadata for this project, Druid and HikariCP ships in
dynamic-datasource-native-metadata; starter users pick it up through the native metadata repository.
| Module | Responsibility |
|---|---|
dynamic-datasource-creator |
Datasource creation: pool adapters (Druid/HikariCP/BeeCP/DBCP2/C3P0/Oracle UCP/basic), XA resources, crypto helpers |
dynamic-datasource-spring |
Core: DynamicRoutingDataSource, group/item datasources, @DS AOP, holder stack, transaction engine, strategies, plugins |
dynamic-datasource-spring-boot-common |
Shared DynamicDataSourceProperties and auto-configuration building blocks |
dynamic-datasource-spring-boot-starter |
Spring Boot 1.5.x ~ 2.x.x (javax.*) entry point |
dynamic-datasource-spring-boot3-starter |
Spring Boot 3.x (jakarta.*) entry point |
dynamic-datasource-spring-boot4-starter |
Spring Boot 4.x entry point |
dynamic-datasource-native-metadata |
GraalVM reachability metadata shipped as an artifact |
dynamic-datasource-test-common |
Shared test fixtures, not published |
Facing a problem? Check the Wiki FAQ first. The most frequently reported issues —
@DSnot switching, startup errors, switching inside transactions, threads/async, pool parameters not taking effect — are distilled into troubleshooting checklists; most questions are answered there. File an issue only when it is a genuine bug or not covered by the FAQ.
- Documentation — https://doc.xiuceyun.cn: the complete guide — tutorials, configuration keys, integrations and advanced usage, with a Chinese/English switcher and versioned docs (the former paid content is now free for everyone).
- Wiki (English) — https://github.com/baomidou/dynamic-datasource/wiki/Home-English
- Wiki (简体中文) — https://github.com/baomidou/dynamic-datasource/wiki The Wiki focuses on FAQ & troubleshooting checklists distilled from hundreds of historical issues; treat the documentation site as the complete reference.
- FAQ picks — @DS not working checklist · switching inside transactions · startup errors · threads & async · all FAQs
- Samples — https://github.com/dynamic-datasource/dynamic-datasource-samples
- QQ group (Chinese-speaking community) — join
- Legacy docs (Kancloud, historical reference only) — https://www.kancloud.cn/tracy5546/dynamic-datasource
This repository ships an Agent Skill in the open SKILL.md format (skills/dynamic-datasource). Once installed into your project, AI coding assistants (Qoder / Claude Code / Cursor, …) can answer, in your project's context: basic installation & minimal configuration, and troubleshooting for the most common problems — "why is datasource switching failing / @DS not working", startup errors, switching inside transactions, threads/async, pool parameters, password encryption.
Install (any one):
- skills CLI:
npx skills add baomidou/dynamic-datasource - Manual: copy the
skills/dynamic-datasource/directory into your project's.qoder/skills/(Qoder) or.claude/skills/(Claude Code) directory - Or simply paste the
SKILL.mdlink to your AI assistant and let it answer from this context
- Contributions are welcome — read CONTRIBUTING.md for the Gradle/toolchain
setup, the
nativeTestgate and the release procedure. - Please do not open a public issue for a vulnerability; follow SECURITY.md.
- Released under the Apache License 2.0. Maintained since 2018 by the baomidou organisation; the canonical repository is baomidou/dynamic-datasource.
