不能直接用 sharding-jdbc 或 dynamic-datasource 对 Oracle 做读写分离:Oracle 官方不支持 SELECT 自动路由到只读实例,Data Guard 从库默认只读,且 sharding-jdbc 的 SQL 类型识别在 Oracle 中极易误判(如 FOR UPDATE、PL/SQL 混合逻辑),必须由代码层显式控制路由;AbstractRoutingDataSource 是唯一可靠起点,需结合 ThreadLocal + @ReadOnlyConnection 注解 + AOP(@Order(HIGHEST_PRECEDENCE))实现安全切换,并兜底处理复制延迟、ORA-16000 错误及事务一致性。

不能直接用 sharding-jdbc 或 dynamic-datasource 对 Oracle 做读写分离:Oracle 官方不支持像 MySQL 那样的 SELECT 自动路由到只读实例,且其主从(Data Guard)模式下从库默认为只读、不接受 DML,但 Spring 的自动 SQL 类型识别(如 sharding-jdbc 的 INSERT/UPDATE/DELETE 判定)在 Oracle 中极易误判——比如带 FOR UPDATE 的 SELECT 实际是写意图,或 PL/SQL 块里混写读写逻辑。所以必须由代码层显式控制路由,而非依赖 SQL 解析。
AbstractRoutingDataSource 是唯一可靠起点
Spring 的 AbstractRoutingDataSource 不关心数据库类型,只按你返回的 key 查表匹配真实数据源。对 Oracle 主从,你需要两个独立配置的 DataSource(主库可读写,从库仅读),再通过一个线程级上下文决定当前用哪个 key。
-
RoutingDataSource继承AbstractRoutingDataSource,重写determineCurrentLookupKey(),返回"master"或"slave" - 用
ThreadLocal持有当前意图,比如DbContextHolder.set("slave") - 务必在每次请求结束时调用
DbContextHolder.clear(),否则线程复用(如 Tomcat 线程池)会导致后续请求继承错误数据源 - Oracle 从库若启用了 Active Data Guard(即允许只读访问),连接 URL 必须显式加上
?readOnly=true,否则部分驱动会尝试协商写权限并报错
@ReadOnlyConnection 注解 + AOP 是最稳的切面方案
把 @Transactional 和 @ReadOnlyConnection 同时加在一个方法上是危险的:Spring 事务管理器绑定的是 DataSource,而 AOP 在事务开启前就要切换数据源,顺序错乱会导致事务失效或连接不一致。
- AOP 切面必须用
@Order(Ordered.HIGHEST_PRECEDENCE),确保在TransactionInterceptor之前执行 - 切点表达式建议限定为
@annotation(com.example.annotation.ReadOnlyConnection),避免拦截@Transactional方法体内的嵌套调用 - 注解应支持
fallbackToMaster = true属性:当从库不可用时,自动降级走主库,而不是抛NoSuchBeanException - 不要在异步方法(
@Async)里用该注解——ThreadLocal不会自动传递,需手动inheritableThreadLocal或显式传参
事务与一致性必须人工兜底
Oracle Data Guard 存在复制延迟,INSERT 后立刻 SELECT 可能查不到刚写入的数据。这不是框架能解决的,得靠业务逻辑收敛。
- 强一致性读(如订单创建后立即查详情)必须强制走主库,哪怕加了
@ReadOnlyConnection也应被忽略 - 可在 DAO 层封装
selectForUpdateOnMaster(...)方法,内部直接调用DbContextHolder.set("master") - 避免在同一个事务里跨数据源操作:Spring 的
DataSourceTransactionManager只管一个DataSource,混合使用主/从连接会触发TransactionSuspensionNotSupportedException - 如果用 JPA,
@Query的原生 SQL 若含SELECT ... FOR UPDATE,必须走主库,且不能依赖注解自动识别——得靠命名规范(如方法名含WithLock)或额外标记
真正难的不是切换数据源,而是让每个查询都清楚自己该走哪条路,以及当它走错时系统能不能及时发现并止损。Oracle 的只读从库不像 MySQL 那样“宽容”,一次 INSERT 发到从库就会直接炸开连接,日志里只有一行 ORA-16000: database open for read-only access ——这个错误不会被 Spring 吞掉,它会一路冒泡到 controller,所以监控和告警必须覆盖 ORA-16000 这个码。











