125 如何运行时实时切换数据库(实时切换影子库)

前言

最近有 流量回放 的使用需求

然后 看了一下 影子库 的相关知识

然后 在考虑 怎么运行时 直接通过 配置项的更新 来切换 业务项目的数据库路由是走 生产库, 还是影子库

搜索了一下 大多数的使用貌似 都不怎么 符合这个灵活的需求, 因此 组合根据这里的需要 造个轮子

复制代码
jerry.shadow.db.feature.enabled 作为一个 影子库切换 feature 的一个总开关, 关闭的话 则使用默认的 DataSource, SqlSessionFactory 不会有任何额外的业务影响, 主要是用于 假设这个 feature 出现什么问题, 可以及时的无副作用的关掉 
复制代码
jerry.shadow.db.use-shadow-db 配置为 true 表示使用影子库, 其他情况表示使用默认库 

JerryShadowDBConfig

这里是直接创建的 SimpleDataSource, 实际业务中一般是 使用的 Druid 之类的数据源

拷贝一下 其对应的 DruidDataSourceProperties, 以及 DruidDataSourceWrapper 然后 更新一下 prefix, 然后 构造一下对应的 bean 即可

这里主要的目的是构造 JerryShadowDBSqlSessionFactory 来作为项目的 SqlSessionFactory, 其中会根据配置进行路由是 业务数据库 还是 影子数据库

复制代码
package com.hx.boot.config;

/**
 * MyBatisConfig
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2025-09-04 10:55
 */
@Configuration
@ConditionalOnProperty(name = "jerry.shadow.db.feature.enabled", havingValue = "true")
public class JerryShadowDBConfig {

    @Resource
    private ApplicationContext applicationContext;

    @Bean
    public DataSource defaultDataSource() {
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
        dataSource.setDriverClass(Driver.class);
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&relaxAutoCommit=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true");
        dataSource.setUsername("root");
        dataSource.setPassword("postgres");
        return dataSource;
    }

    @Bean
    public SqlSessionFactory defaultSqlSessionFactory(@Qualifier("defaultDataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        return factory.getObject();
    }

    @Bean
    public DataSource shadowDataSource() {
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
        dataSource.setDriverClass(Driver.class);
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/shadow_db?useUnicode=true&characterEncoding=utf-8&relaxAutoCommit=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true");
        dataSource.setUsername("root");
        dataSource.setPassword("postgres");
        return dataSource;
    }

    @Bean
    public SqlSessionFactory shadowSqlSessionFactory(@Qualifier("shadowDataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        return factory.getObject();
    }

    @Bean
    @Primary
    public SqlSessionFactory jerryShadowDBSqlSessionFactory(@Qualifier("defaultSqlSessionFactory") SqlSessionFactory defaultSqlSessionFactory,
                                                            @Qualifier("shadowSqlSessionFactory") SqlSessionFactory shadowSqlSessionFactory) throws Exception {
        return new JerryShadowDBSqlSessionFactory(defaultSqlSessionFactory, shadowSqlSessionFactory, applicationContext.getEnvironment());
    }


}

JerryShadowDBSqlSessionFactory

组合业务数据库 和 影子数据库

实时 根据上下文的配置进行路由

复制代码
package com.hx.boot.config;

/**
 * JerryShadowDBSqlSessionFactory
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2025-09-04 10:55
 */
public class JerryShadowDBSqlSessionFactory implements SqlSessionFactory {

    private SqlSessionFactory defaultSqlSessionFactory;

    private SqlSessionFactory shadowSqlSessionFactory;

    private Environment env;

    public JerryShadowDBSqlSessionFactory(SqlSessionFactory defaultSqlSessionFactory, SqlSessionFactory shadowSqlSessionFactory, Environment env) {
        this.defaultSqlSessionFactory = defaultSqlSessionFactory;
        this.shadowSqlSessionFactory = shadowSqlSessionFactory;
        this.env = env;
    }

    @Override
    public SqlSession openSession() {
        return chooseSqlSessionFactory().openSession();
    }

    @Override
    public SqlSession openSession(boolean autoCommit) {
        return chooseSqlSessionFactory().openSession(autoCommit);
    }

    @Override
    public SqlSession openSession(Connection connection) {
        return chooseSqlSessionFactory().openSession(connection);
    }

    @Override
    public SqlSession openSession(TransactionIsolationLevel level) {
        return chooseSqlSessionFactory().openSession(level);
    }

    @Override
    public SqlSession openSession(ExecutorType execType) {
        return chooseSqlSessionFactory().openSession(execType);
    }

    @Override
    public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
        return chooseSqlSessionFactory().openSession(execType, autoCommit);
    }

    @Override
    public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
        return chooseSqlSessionFactory().openSession(execType, level);
    }

    @Override
    public SqlSession openSession(ExecutorType execType, Connection connection) {
        return chooseSqlSessionFactory().openSession(execType, connection);
    }

    @Override
    public Configuration getConfiguration() {
        return chooseSqlSessionFactory().getConfiguration();
    }


    private SqlSessionFactory chooseSqlSessionFactory() {
        String useShadowDb = env.getProperty("jerry.shadow.db.use-shadow-db");
        if("true".equalsIgnoreCase(useShadowDb)) {
            return shadowSqlSessionFactory;
        }
        return defaultSqlSessionFactory;
    }


}

效果测试

测试的 api 如下, 很简单 就是 mybatis 的 Mapper 进行 查询

test/shadow_db 中数据库分别如下

复制代码
jerry.shadow.db.feature.enabled 作为一个 影子库切换 feature 的一个总开关, 关闭的话 则使用默认的 DataSource, SqlSessionFactory 不会有任何额外的业务影响, 主要是用于 假设这个 feature 出现什么问题, 可以及时的无副作用的关掉 
复制代码
jerry.shadow.db.use-shadow-db 配置为 false/true 的结果分别如下 

可以看到 实现了 数据库的切换

完

相关推荐
驭渊的小故事4 小时前
MyBatis XML 开发实战:从配置到 CRUD 全解析
xml·java·mybatis
哦虎!9 小时前
【JavaEE】网络初识
java
for_ever_love__9 小时前
MySQL ORDER BY 排序优化:从 Using filesort 到索引有序扫描(含双路 / 单路排序原理)
java·mysql·优化·存储·by·order·filesort
我是人✓10 小时前
Java文件上传三方式对比:从传统到SpringBoot
java·开发语言
欣栀♡10 小时前
JVM 频繁 Full GC 先看日志括号里的 cause,jstat 的 FGC 在 JDK 8 上会骗人
java·jvm
IT研究室11 小时前
最新计算机毕业设计选题推荐-基于spring boot的葡萄酒文化宣传网站-网站-文档指导-Java-springboot
java·spring boot·课程设计
慧都小项11 小时前
MyEclipse 中创建和测试 JAX-RS REST 接口:从项目配置到 REST Explorer
java·restful·myeclipse·jax-rs
Wang's Blog11 小时前
Java 项目实战: 交友项目 - 入门案例:编写 Dubbo 服务提供者
java·dubbo·交友
秋饼11 小时前
Spring AI 2.0 结构化输出企业级实战:从 BeanOutputConverter 源码到生产级解析容错
java·ai·技术分享·后端开发
摇滚侠11 小时前
《计算机是如何工作的:人人都能懂的计算机软硬件工作原理》第 9 章 机器码与汇编语言 阅读笔记 9
java·开发语言·笔记