AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
Spring Boot용 동적 데이터소스로 다중 데이터원·마스터·슬레이브·읽기·쓰기·분산 트랜잭션 지원
dynamic datasource for springboot 多数据源 动态数据源 主从分离 读写分离 分布式事务
개발 도구Java · PowerShellGitHub @baomidou직접 설치운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗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).
<!-- 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>
// 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.Driver
The 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").
spring.datasource.dynamic.datasource._ is the group name; names sharing a group are pooled into one GroupDataSource.strategy (load balance by default, RandomDynamicDataSourceStrategy optional).master, overridable via spring.datasource.dynamic.primary.DynamicDataSourceContextHolder.push) > method annotation > class annotation, and @DS inherited from interfaces or abstract classes is honoured.ServiceA >>> ServiceB >>> ServiceC nesting 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/dynamic
Supported 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 reference
SpEL 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.AbstractJdbcDataSourceProvider lets you load the
catalogue itself from a bootstrap database.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-datasource public-key, and EncDataSourceInitEvent
as the hook for your own algorithm (AES, SM4, KMS…).p6spy: true wraps datasources for SQL logging; seata: false opts a single datasource out of proxying.DataSource; MasterSlaveAutoRoutingPlugin gives
pure read/write splitting with no annotation at all (SELECT → slave, everything else → master).DataSource; the documented recipe covers the JDBC job store.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.
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):
npx skills add baomidou/dynamic-datasourceskills/dynamic-datasource/ directory into your project's .qoder/skills/ (Qoder) or .claude/skills/ (Claude Code) directorySKILL.md link to your AI assistant and let it answer from this contextnativeTest gate and the release procedure.