Spring通过拦截器实现多数据源切换的示例代码

 更新时间:2025年08月20日 10:02:16   作者:南姜先生  
本文主要介绍了Spring拦截器实现多数据源动态切换,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

在 Spring 应用中,特别是使用了多数据源的场景下,可以通过**拦截器(Interceptor)**机制来实现动态数据源切换。Spring 提供了多种方式来管理多个数据源,并允许开发者根据业务逻辑的需求灵活地选择不同的数据源进行数据库操作。

下面介绍如何通过拦截器实现多数据源切换的基本思路和具体步骤。

🧠 一句话总结:

✅ 通过自定义 HandlerInterceptor 或 MyBatis 的 Interceptor 拦截请求或 SQL 执行过程,在合适的时机动态设置当前线程的数据源,从而实现基于请求或方法级别的多数据源切换。

🔍 一、基本思路

  1. 配置多个数据源:首先需要在 Spring 配置类中定义多个 DataSource Bean。
  2. 动态数据源路由:创建一个代理数据源,它能够根据某些条件(如当前线程中的某个标识符)选择实际使用的数据源。
  3. 拦截请求或方法调用:使用 Spring MVC 的 HandlerInterceptor 或 MyBatis 的 Interceptor 来拦截请求或方法调用,在执行之前设置当前线程的数据源标识符。
  4. 清除上下文:确保在请求结束后清理线程局部变量,避免影响后续请求。

🛠️ 二、具体实现步骤

1.定义多个数据源

@Configuration
public class DataSourceConfig {

    @Bean(name = "primaryDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
}

2.创建动态数据源路由

public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    @Override
    protected Object determineCurrentLookupKey() {
        return contextHolder.get();
    }

    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    public static String getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
    }
}
  • determineCurrentLookupKey() 方法返回当前应该使用的数据源标识符。
  • 使用 ThreadLocal 变量存储每个线程的数据源标识符。

3.注册动态数据源

@Configuration
public class DynamicDataSourceConfig {

    @Autowired
    @Qualifier("primaryDataSource")
    private DataSource primaryDataSource;

    @Autowired
    @Qualifier("secondaryDataSource")
    private DataSource secondaryDataSource;

    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("primary", primaryDataSource);
        targetDataSources.put("secondary", secondaryDataSource);

        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(primaryDataSource); // 设置默认数据源
        return dynamicDataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        return sessionFactory.getObject();
    }
}

4.编写拦截器

a.Spring MVC 拦截器

@Component
public class DataSourceInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 根据请求路径或其他条件设置数据源
        if (request.getRequestURI().startsWith("/api/secondary")) {
            DynamicDataSource.setDataSource("secondary");
        } else {
            DynamicDataSource.setDataSource("primary");
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        DynamicDataSource.clearDataSource(); // 清理线程局部变量
    }
}

b.MyBatis 拦截器

如果你想要基于 MyBatis 的 @Intercepts 注解来实现,则可以这样做:

@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
             @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class DataSourceInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            // 假设有一个方法可以根据某些条件确定要使用的数据源
            String dataSourceKey = determineDataSourceKey(invocation.getMethod().getName());
            DynamicDataSource.setDataSource(dataSourceKey);

            return invocation.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

5.注册拦截器

a.Spring MVC 拦截器注册

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private DataSourceInterceptor dataSourceInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(dataSourceInterceptor).addPathPatterns("/**");
    }
}

b.MyBatis 拦截器注册

如果你使用的是 MyBatis 拦截器,可以在配置类中添加如下代码:

@Configuration
public class MyBatisConfig {

    @Autowired
    private DataSourceInterceptor myBatisDataSourceInterceptor;

    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setPlugins(new Interceptor[]{myBatisDataSourceInterceptor});
        return sessionFactory.getObject();
    }
}

📦 三、注意事项

  • 线程安全:由于我们使用了 ThreadLocal 来存储数据源标识符,因此在每次请求结束时都必须清理该变量,以防止内存泄漏或跨请求污染。
  • 事务管理:如果涉及到事务,需确保事务与数据源绑定正确。通常情况下,Spring 的事务管理器会自动处理这个问题,但如果你手动管理事务,可能需要额外注意。
  • 性能考虑:频繁地切换数据源可能会带来一定的性能开销,尤其是在高并发场景下。因此,应尽量减少不必要的切换频率。
  • 异常处理:确保在发生异常时也能正确地清理上下文,避免潜在的问题。

到此这篇关于Spring通过拦截器实现多数据源切换的示例代码的文章就介绍到这了,更多相关Spring 拦截器多数据源切换内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java并发高的情况下用ThreadLocalRandom来生成随机数

    java并发高的情况下用ThreadLocalRandom来生成随机数

    如果我们想要生成一个随机数,通常会使用Random类。但是在并发情况下Random生成随机数的性能并不是很理想,本文主要介绍了java并发高的情况下用ThreadLocalRandom来生成随机数,感兴趣的可以了解一下
    2022-05-05
  • 基于springboot拦截器HandlerInterceptor的注入问题

    基于springboot拦截器HandlerInterceptor的注入问题

    这篇文章主要介绍了springboot拦截器HandlerInterceptor的注入问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • Java中线程休眠编程实例

    Java中线程休眠编程实例

    这篇文章主要介绍了Java中线程休眠编程实例,本文直接给出代码实例,并对休眠方法做了一番讲解,需要的朋友可以参考下
    2015-06-06
  • SpringCloud Alibaba Seata (收藏版)

    SpringCloud Alibaba Seata (收藏版)

    Seata是一款开源的分布式事务解决方案,致力于在微服务架构在提供高性能和简单一样的分布式事务服务。这篇文章主要介绍了SpringCloud Alibaba Seata 的相关知识,需要的朋友可以参考下
    2020-10-10
  • eclipse导入工程报错问题项目或者文件有红叉的解决方案

    eclipse导入工程报错问题项目或者文件有红叉的解决方案

    这篇文章主要介绍了eclipse导入工程报错问题项目或者文件有红叉的解决方案,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • 在Spring Data JPA中引入Querydsl的实现方式

    在Spring Data JPA中引入Querydsl的实现方式

    这篇文章主要介绍了在Spring Data JPA中引入Querydsl的实现方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • Spring Boot中JSON数值溢出问题从报错到优雅解决办法

    Spring Boot中JSON数值溢出问题从报错到优雅解决办法

    这篇文章主要介绍了Spring Boot中JSON数值溢出问题从报错到优雅的解决办法,通过修改字段类型为Long、添加全局异常处理和数据校验,解决了该问题,文章还提供了类型范围推荐场景和常见问题解答,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-04-04
  • spring boot 测试单元修改数据库不成功的解决

    spring boot 测试单元修改数据库不成功的解决

    这篇文章主要介绍了spring boot 测试单元修改数据库不成功的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • 深入解析Java中的Classloader的运行机制

    深入解析Java中的Classloader的运行机制

    这篇文章主要介绍了Java中的Classloader的运行机制,包括从JVM方面讲解类加载器的委托机制等,需要的朋友可以参考下
    2015-11-11
  • Springboot如何根据docx填充生成word文件并导出pdf

    Springboot如何根据docx填充生成word文件并导出pdf

    这篇文章主要介绍了Springboot如何根据docx填充生成word文件并导出pdf问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08

最新评论