当前位置: 首页 > news >正文

基于域名的动态数据源切换实现教程

概述

这是一个基于Spring Boot的多数据源动态切换方案,通过解析请求的域名自动选择对应的数据源。

核心组件实现

1. 会话上下文管理 (SessionContext)

  • 使用 TransmittableThreadLocal 实现线程间数据传递
  • 提供统一的键值对存储接口
  • 在请求开始时清理旧数据,在结束时移除数据避免内存泄漏

2. 请求拦截器 (HeaderInterceptor)

  • 在 preHandle 阶段解析请求头中的 Origin
  • 提取域名并存储到 SessionContext 中
  • 调用 SessionContext.remove() 清理线程数据

3.动态数据源实现 (DynamicDataSource)

  • 继承 AbstractRoutingDataSource
  • 重写 determineCurrentLookupKey() 方法
  • 从 SessionContext 获取域名作为数据源标识

4.数据源配置 (DataSourceConfig)

  • 配置多个实际数据源(如 bjDataSource 和 cdDataSource)
  • 构建 targetDataSources 映射表,以域名为键关联具体数据源
  • 设置默认数据源

工作流程

  1. 请求进入 → HeaderInterceptor 解析 Origin 头部
  2. 域名提取 → 将域名存入 SessionContext
  3. 数据源路由 → DynamicDataSource 根据域名选择对应数据源
  4. 执行操作 → 使用选定的数据源执行数据库操作

会话上下文管理

package com.park.context; import cn.hutool.core.convert.Convert; import com.alibaba.ttl.TransmittableThreadLocal; import com.park.constants.SecurityConstants; import org.apache.commons.lang3.StringUtils; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class SessionContext { private static final TransmittableThreadLocal<Map<String, Object>> THREAD_LOCAL = new TransmittableThreadLocal<>(); public static void set(String key, Object value) { Map<String, Object> map = getLocalMap(); map.put(key, value == null ? StringUtils.EMPTY : value); } public static String get(String key) { Map<String, Object> map = getLocalMap(); return Convert.toStr(map.getOrDefault(key, StringUtils.EMPTY)); } public static Map<String, Object> getLocalMap() { Map<String, Object> map = THREAD_LOCAL.get(); if (map == null) { map = new ConcurrentHashMap<>(); THREAD_LOCAL.set(map); } return map; } public static void setCompanyId(String companyId) { set(SecurityConstants.COMPANY_ID, companyId); } public static String getCompanyId() { return get(SecurityConstants.COMPANY_ID); } public static void setParkingLotId(Set<String> parkingLotIds) { set(SecurityConstants.PARKINGLOT_ID, parkingLotIds); } public static Set<String> getParkingLotId() { return get(SecurityConstants.PARKINGLOT_ID, Set.class); } public static <T> T get(String key, Class<T> clazz) { Map<String, Object> map = getLocalMap(); return cast(map.getOrDefault(key, null)); } public static <T> T cast(Object obj) { if (obj == null) { return null; } return (T) obj; } public static void remove() { THREAD_LOCAL.remove(); } }

核心动态数据源类

package com.park.source; import com.park.constants.SecurityConstants; import com.park.context.SessionContext; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { System.out.println("当前数据源:" + SessionContext.get(SecurityConstants.DOMAIN)); return SessionContext.get(SecurityConstants.DOMAIN); } }

请求拦截器

package com.park.constants; public class SecurityConstants { public static final String AUTHORIZATION = "Authorization"; public static final String COMPANY_ID = "companyId"; public static final String PARKINGLOT_ID = "parkingLotIds"; public static final String DOMAIN = "domain"; } package com.park.interceptor; import com.park.constants.SecurityConstants; import com.park.context.SessionContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.AsyncHandlerInterceptor; import java.net.URI; import java.net.URISyntaxException; @Component public class DomainInterceptor implements AsyncHandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 清除之前的会话数据 SessionContext.remove(); String origin = request.getHeader("Origin"); if (origin != null) { try { URI uri = new URI(origin); String domain = uri.getHost(); SessionContext.setDomain(domain); } catch (URISyntaxException e) { // 记录错误日志 } } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 请求结束后清除线程本地变量 SessionContext.remove(); } } package com.park.config; import com.park.interceptor.DomainInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private DomainInterceptor domainInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(domainInterceptor) .addPathPatterns("/**") .excludePathPatterns("/login", "/public/**"); } }

数据源配置

spring: datasource: bj: jdbc-url: jdbc:postgresql://localhost:5432/bj_db username: your_username password: your_password driver-class-name: org.postgresql.Driver cd: jdbc-url: jdbc:postgresql://localhost:5432/cd_db username: your_username password: your_password driver-class-name: org.postgresql.Driver package com.park.config; import com.park.source.DynamicDataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Configuration public class DataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource.bj") public DataSource bjDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.cd") public DataSource cdDataSource() { return DataSourceBuilder.create().build(); } @Bean @Primary public DataSource dynamicDataSource() { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("bj.park.com", bjDataSource()); targetDataSources.put("cd.park.com", cdDataSource()); DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.setDefaultTargetDataSource(primaryDataSource()); return dynamicDataSource; } }
http://www.jsqmd.com/news/241184/

相关文章:

  • SPI控制器功能验证实践:基于iverilog的端到端流程
  • 【毕业设计】SpringBoot+Vue+MySQL 信息化在线教学平台平台源码+数据库+论文+部署文档
  • 零基础学习指南:STLink驱动安装全过程
  • u8g2 OLED配置教程:手把手教你写第一行代码
  • 手把手教程:使用esptool实现加密固件烧录
  • 【2025最新】基于SpringBoot+Vue的房屋租赁管理系统管理系统源码+MyBatis+MySQL
  • 基于STM32F4的GPIO初始化STM32CubeMX教程实战案例
  • 图解说明Keil MDK中ARM Compiler 5.06的编译输出流程
  • Multisim14.0交流小信号分析操作指南:通俗解释
  • I2C HID协议时序分析:实战案例解析
  • AUTOSAR经典平台入门:ECU抽象层全面讲解
  • 企业级个人理财系统管理系统源码|SpringBoot+Vue+MyBatis架构+MySQL数据库【完整版】
  • XADC IP核在嵌入式监控中的项目应用
  • 前后端分离论坛网站系统|SpringBoot+Vue+MyBatis+MySQL完整源码+部署教程
  • 74194双向移位时序分析:超详细版时序图讲解
  • 什么是营销管理系统,一文说清:定义、功能、选型、产品推荐
  • Java Web 游戏销售平台系统源码-SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0【含文档】
  • BL370 为什么原生支持 Docker?这是为工业现场提前铺好的路
  • 做小红书 3 年,我终于悟了:废掉你账号的不是内容,而是那张“丑封面”(附 01Agent 实操避坑指南)
  • ARM开发深度剖析:STM32中断系统NVIC全面讲解
  • Java SpringBoot+Vue3+MyBatis 个人理财系统系统源码|前后端分离+MySQL数据库
  • python 代码扫描 icmp 时间戳漏洞 ICMP Timestamp Request Remote Date Disclosure
  • 别再把树莓派当玩具了,它已经能胜任工业级 AI 控制器
  • PLC标准IEC61499 vs IEC61131:自动化工程师必须搞懂的核心区别
  • 设备树与传统板级文件对比:一文说清差异
  • CubeMX入门必看:STM32配置基础快速理解
  • 商米科技冲刺港股:9个月营收22亿利润5608万 已获IPO备案
  • iNeuOS工业互联网操作系统,实现能源管理及应用案例
  • 图解说明Multisim数据库中符号与封装的映射关系
  • XGSLab | 接地系统和电磁分析软件视频教程