news 2026/6/14 23:11:25

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于域名的动态数据源切换实现教程

概述

这是一个基于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; } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/15 11:27:26

vitis安装实战案例:结合Vivado实现一体化平台部署

手把手部署Vitis Vivado一体化开发平台&#xff1a;从零搭建Zynq MPSoC图像处理系统 你有没有遇到过这样的场景&#xff1f; 在FPGA项目中&#xff0c;刚用Vivado完成硬件设计&#xff0c;导出 .xsa 文件准备进软件开发时&#xff0c;Vitis却提示“无法识别平台”&#xf…

作者头像 李华
网站建设 2026/6/15 12:14:42

谷歌团队在Nature发表的“标杆性成果”,被指不可靠

“谷歌团队在Nature发表的论文并不可靠&#xff01;”近日&#xff0c;英国利物浦大学的数学家和计算机科学家维塔利库尔林&#xff08;Vitaliy Kurlin&#xff09;团队公开指出&#xff0c;谷歌DeepMind旗下人工智能工具GNoME&#xff08;材料探索图网络&#xff09;所生成的晶…

作者头像 李华
网站建设 2026/6/13 15:41:17

浏览器插件到底安全不?教你几招快速检测方法

随着我们日常上网的频率越来越高&#xff0c;浏览器插件已经成为不少人的必备工具。无论是广告拦截、密码管理&#xff0c;还是视频下载&#xff0c;插件确实能让我们的网络生活更方便。 不过&#xff0c;你有没有想过&#xff0c;你安装的插件到底安全不安全&#xff1f;有些…

作者头像 李华
网站建设 2026/6/15 12:35:28

CCS20新手教程:手把手带你熟悉开发环境

掌握TI嵌入式开发的钥匙&#xff1a;CCS20实战入门指南你是否曾在启动一个C2000项目时&#xff0c;面对Code Composer Studio那复杂的界面无从下手&#xff1f;是否下载程序失败、变量监视失效、断点无法命中&#xff0c;反复重启却找不到原因&#xff1f;别担心——这几乎是每…

作者头像 李华