Skip to main content
Version: 4.6

@DS Not Working Checklist

Related issues: #770, #649, #674, #266, #122, #166, #740, #663

After marking a method with @DS("xxx") the SQL still hits the default database (primary), or the log says "it switched but the connection did not change" โ€” this is the single most reported class of problem in this repository (150+ issues in total). Go through the list below in order; it is sorted by hit rate, highest first.

Step 0: read the log firstโ€‹

Turn on the framework's logging and see what it actually says:

logging:
level:
com.baomidou.dynamic.datasource: debug
Log you seeMeaningWhere to go
dynamic-datasource switch to the datasource named [slave_1]the key was resolved correctly, the switch happenedjump to items 2 / 6
dynamic-datasource switch to the primary datasourceno valid key at all, it fell back to the primaryitems 1, 3, 5
neither linethe annotated method was never intercepted by AOPitems 1, 3, 4

Checklistโ€‹

1. Does the call go through the Spring proxy?โ€‹

@DS is essentially a Spring AOP aspect. The following calls do not pass through the proxy, so the annotation is dead weight:

  • a self-invocation this.method() inside the same class โ€” #770, #15;
  • static methods, objects you created with new โ€” #649;
  • utility classes not managed by Spring (not a bean).

Fix: call across beans; or self-inject (@Autowired private SelfService self;) and call through that; turn static utility classes into injected proxy beans.

2. Is there an outer Spring transaction already?โ€‹

A transaction opened by @Transactional binds the primary's connection first; changing the key with @DS mid-transaction cannot replace the already bound connection. The switch must happen before the transaction starts.

See no switching inside a transaction.

3. Method visibility and annotation placementโ€‹

  • with the default spring.datasource.dynamic.aop.allowed-public-only=true, only public methods are intercepted (#266);
  • prefer methods of the Service implementation class; annotating interface methods is recognised too (the framework looks the implementation up), but with version and scenario limitations โ€” see @DS placement and MyBatis-Plus call styles;
  • when both a class-level and a method-level annotation exist, the method wins; annotations on interfaces/superclasses are inherited.

The complete precedence chain is in @DS semantics and precedence.

4. Did a MyBatis-Plus call style bypass it?โ€‹

lambdaQuery().list(), IService default methods, direct BaseMapper calls, ActiveRecord and similar scenarios are very easy to get wrong (35+ historical issues).

See @DS placement and MyBatis-Plus call styles.

5. Does the key really exist?โ€‹

  • check spelling, case, and the group name rule (everything before the first _ is the group name: slave_1 belongs to the slave group, so @DS("slave") switches to the group);
  • when the key does not exist, non-strict mode silently falls back to the primary โ€” this is exactly the most hidden cause of "the annotation is there but the primary is always used";
  • turn on strict mode so the problem surfaces immediately:
spring:
datasource:
dynamic:
strict: true # an unmatched datasource throws CannotFindDataSourceException instead of quietly falling back to primary

6. Is a leftover manual push overriding it?โ€‹

If an exception path skips the poll() after a DynamicDataSourceContextHolder.push("xx"), later calls on that thread are affected by the leftover key (showing up as "cross-database contamination"). Rule: push must always be paired with try/finally + poll or clear.

See manual datasource switching.

7. Did the call cross a thread boundary?โ€‹

When execution happens inside @Async, a thread pool, CompletableFuture or parallelStream, the resolved @DS value is not propagated to the child thread, which defaults to the primary.

See @DS and threads.

8. Is it too early?โ€‹

Calling from @PostConstruct, InitializingBean, an early ApplicationRunner or during bean initialization means the upstream proxy/context is not ready yet, so the switch has no effect (#122). Move database-switching work onto the business request path and move initialization logic into a ContextRefreshedEvent listener โ€” see troubleshooting switching failures.

9. Does it not work on a unit test class?โ€‹

@DS must be placed on a bean method inside the Spring container. Annotating the test class itself has no effect (#153, #674).

10. Verify with a breakpointโ€‹

Add one line inside the business method:

String current = DynamicDataSourceContextHolder.peek();
  • null: the annotation was never intercepted (back to items 1, 3, 4);
  • the expected value but still the wrong database: item 2 (the connection is already bound by a transaction) or item 6 (a leftover value).

Symptom โ†’ most likely causeโ€‹

SymptomMost likely cause
always the primary, no switch to the datasource in the logthe annotation was not intercepted (self-invocation / non-public / not a bean / bypassed by MP)
calling a method in the same class does not switchthis self-invocation, item 1
switching inside a transactional method has no effectthe transaction already bound a connection, item 2
chained queries in a Controller do not switchMyBatis-Plus style, item 4
occasional cross-database contaminationpush leftovers / thread reuse, items 6 and 7
it switched but a table is reported missingthe key does not exist (fell back to primary), item 5

Further readingโ€‹