必须用abstractroutingdatasource,因其在每次getconnection()前强制执行determinecurrentlookupkey()实现动态路由;手动new多个datasource会导致事务失效、连接泄漏、sqlsession复用脏数据及线程复用走错库。

必须用 AbstractRoutingDataSource,其他方式在 Oracle 场景下大概率事务失效或连接泄漏。
为什么不能直接 new 多个 DataSource 手动传入 DAO?
Oracle 连接开销大、事务上下文强绑定。手动切换 DataSource 会导致:PlatformTransactionManager 仍指向默认数据源,@Transactional 对非默认库完全无效;MyBatis 的 SqlSession 可能复用旧连接,查到脏数据或报 ORA-01000: maximum open cursors exceeded;线程池中线程复用时,ThreadLocal 没清理干净,后续请求静默走错库。
正确做法是让 Spring 在每次 getConnection() 前强制路由——这正是 AbstractRoutingDataSource 的设计目的。
application.yml 中 Oracle 多数据源的缩进与驱动类名陷阱
Spring Boot 2.4+ 会严格校验 @ConfigurationProperties 绑定,缩进错一位、多一个空格,整个数据源配置就静默失效。
-
driver-class-name必须写成oracle.jdbc.OracleDriver(注意大小写,不是oracle.jdbc.driver.OracleDriver,后者已弃用) - URL 必须带
?oracle.net.disableOob=true,否则高并发下连接随机中断 - PDB 实例必须用双斜杠格式:
jdbc:oracle:thin:@//192.168.1.100:1521/orclpdb,写成单斜杠会连不上 - 每个数据源的
url、username、password必须和同级driver-class-name对齐,例如:
spring:
datasource:
db1:
url: jdbc:oracle:thin:@//192.168.1.100:1521/orclpdb?oracle.net.disableOob=true
username: app_user
password: app_pass
driver-class-name: oracle.jdbc.OracleDriver
db2:
url: jdbc:oracle:thin:@//192.168.1.101:1521/orclpdb?oracle.net.disableOob=true
username: rpt_user
password: rpt_pass
driver-class-name: oracle.jdbc.OracleDriver
每个 Oracle 数据源必须配独立的 SqlSessionFactory 和 MapperScan
MyBatis 默认只认一个 SqlSessionFactory。如果多个 Oracle 数据源共用同一个,会出现“Mapper 找不到”或“所有 SQL 都发给 db1”。
关键点:
- 为
db1单独定义@Bean的SqlSessionFactory,setDataSource指向db1DataSource -
@MapperScan的basePackages和sqlSessionFactoryRef必须显式指定,例如:@MapperScan(basePackages = "com.example.mapper.db1", sqlSessionFactoryRef = "db1SqlSessionFactory") - 事务管理器也得一对一:
@Bean("db1TransactionManager") public PlatformTransactionManager db1TxManager(@Qualifier("db1DataSource") DataSource ds)
determineCurrentLookupKey() 返回 null 不报错,但会 fallback 到默认库
这是最常被误判为“切换失败”的地方。当 DynamicDataSourceContextHolder.getDataSourceType() 返回 null 时,AbstractRoutingDataSource 不会抛异常,而是自动使用 setDefaultTargetDataSource() 设置的那个数据源。
排查步骤:
- 检查 AOP 切面是否在
@DS("db2")方法入口调用了setDataSource("db2") - 确认切面是否捕获了异常并在
finally块里执行clear()—— 否则线程池中线程残留旧值 - 避免在
finally清理后又调 DAO:MyBatis 可能复用当前线程的SqlSession,导致后续查询走错库
动态切换的核心不在“怎么设”,而在“什么时候清、清不干净会怎样”。Oracle 连接不像 MySQL 那样容忍粗放管理,线程上下文漏清理一次,可能引发数小时难以复现的跨库查询问题。











