Oracle高可用数据源切换必须用AbstractRoutingDataSource自定义路由,因Spring Boot原生不支持RAC/Data Guard自动故障转移;需结合健康检查、线程绑定、驱动版本匹配及事务隔离等机制实现可靠切换。
AbstractRoutingDataSource 自定义路由spring boot 原生不提供 oracle 高可用(如 rac、data guard 切换)的自动故障转移能力,spring.datasource.url 写死单个地址时,一旦实例宕机就直接抛 sqlexception: io error: connection reset。必须自己实现路由逻辑,让连接在多个 oracle 实例间动态选择。
关键点在于:不能只靠 JDBC URL 的 failover 参数(如 failover=true&loadBalance=true),Oracle 官方驱动对 RAC 的透明应用故障转移(TAF)仅支持 OCI 连接,Thin Driver 无法触发 TAF 回调;而 Data Guard 的角色切换(primary ⇄ standby)更需应用层感知状态并主动重连。
AbstractRoutingDataSource,重写 determineCurrentLookupKey()
ThreadLocal 绑定当前会话期望的数据源 ID(如 "rac-node1" 或 "dg-standby"),而非全局轮询SELECT 1 FROM DUAL),把不可用节点从候选列表中临时剔除@Transactional 方法,在事务开始前校验目标数据源是否存活,不存活则抛自定义异常或 fallback 到备用源ojdbc8 驱动版本与连接参数必须匹配 Oracle 版本用错驱动或参数会导致连接成功但后续执行失败,典型现象是:能连上、能查 DUAL,但一跑含序列/LOB/高级队列的 SQL 就报 ORA-00904: invalid identifier 或 ORA-22922: nonexistent LOB value。
例如 Oracle 19c RAC 环境下,若用 ojdbc6,即使配置了 oracle.net.TNS_ADMIN 指向 tnsnames.ora,也无法解析 SCAN 地址中的负载均衡信息;而 ojdbc8(对应 JDK 8+)才完整支持 12c+ 的新特性,包括 Application Continuity 和 Transparent Application Failover 的基础协议字段。
com.oracle.database.jdbc:ojdbc8:21.10.0.0(Maven 中央库最新稳定版)jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=node1)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=node2)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=orcl)(FAILOVER_MODE=(TYPE=SELECT)(METHOD=BASIC)(RETRIES=3)(DELAY=5)))
oracle.jdbc.useFetchSizeWithLongColumn(默认 true),否则大字段查询可能内存溢出在同一个 @Transactional 方法里,先查主库再写备库,或中间调用 DataSourceContextHolder.set("dg-standby"),会导致 Spring 的 DataSourceTransactionManager 拿到错误的物理连接 —— 事务已绑定初始数据源,后续切换只会让非事务操作“看似”走新库,但实际被回滚或隔离失效。
真实场景中常见错误:用户服务调用订单服务,订单服务内部用了 @DS("dg-standby") 注解,结果事务提交后,主库没更新、备库却写了脏数据,最终 DG 同步失败且无法回退。
@DataSource 或自定义 @DS)必须加在 **非事务方法** 上,或确保该方法本身不参与外层事务(@Transactional(propagation = Propagation.NOT_SUPPORTED))@Transactional(readOnly = true) 并配专用数据源validationQuery 对 Oracle 要设为 SELECT 1 FROM DUAL
很多团队沿用 MySQL 的 SELECT 1,但在 Oracle 下 Druid 会静默忽略校验,导致连接池长期持有已断开的连接,直到下次真正使用时才暴露 SQLException: Closed Connection,拖慢整个请求链路。
Druid 默认的 testWhileIdle + timeBetweenEvictionRunsMillis 机制依赖 validationQuery 执行结果判断连接有效性,Oracle 没有 SELECT 1 语法,必须显式指定 DUAL 表。
spring.datasource.druid.validation-query: SELECT 1 FROM DUAL
spring.datasource.druid.test-on-borrow: false(避免每次取连接都查一次,影响性能),靠空闲检测兜底即可dynamic-datasource-spring-boot-starter,它底层也基于 Druid,同样要覆盖该配置,否则多数据源场景下某个库的连接池会持续泄漏最易被忽略的是:RAC 节点间状态不同步时,健康检查可能误判。比如一个节点刚重启、SMON 还没完成实例恢复,此时 SELECT 1 FROM DUAL 能通,但 INSERT INTO ... 仍会报 ORA-03113: end-of-file on communication channel。建议在生产环境把健康检查升级为执行轻量级业务语句(如查一个带索引的小表主键),而非仅依赖 DUAL。