news 2026/4/23 12:00:41

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

作者头像

张小明

前端开发工程师

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/4/12 2:33:35

u8g2 OLED配置教程:手把手教你写第一行代码

手把手带你用u8g2点亮OLED&#xff1a;从零写出第一行显示代码你有没有过这样的经历&#xff1f;买了一块OLED屏&#xff0c;接上ESP32或STM32&#xff0c;打开Arduino IDE&#xff0c;却卡在“怎么让它亮起来”这一步&#xff1f;查资料发现一堆术语&#xff1a;IC、SSD1306、…

作者头像 李华
网站建设 2026/4/23 11:28:23

核心要点:STLink驱动安装教程中的权限问题处理

ST-Link权限配置实战&#xff1a;从“Permission Denied”到即插即用的工程化路径 你有没有遇到过这样的场景&#xff1f;刚把ST-Link插上Linux电脑&#xff0c;兴冲冲打开VS Code准备调试STM32代码&#xff0c;结果OpenOCD报错&#xff1a; Error: open failed: Permission …

作者头像 李华
网站建设 2026/4/17 23:19:25

摩擦纳米发电机高效波浪能收集方法研究:基于光学动作捕捉浮子俯仰角及摇摆性能提供高精度验证数据 |中科院一区期刊王中林院士团队案例

导语随着清洁能源需求不断增加&#xff0c;海洋波浪能作为一种可再生能源受到广泛关注。中国科学院北京纳米能源与系统研究所王中林院士、曹南颖副研究员团队提出了一种摩擦纳米发电机&#xff08;TENG&#xff09;&#xff0c;结合导电3D打印与浮力-重力优化&#xff0c;实现高…

作者头像 李华
网站建设 2026/4/23 11:22:31

绿电直供与源网荷储一体化——探索零碳产业园区的能源闭环路径

在全球绿色低碳转型的大背景下&#xff0c;如何构建高效、清洁、可持续的区域能源体系&#xff0c;成为推动经济社会高质量发展的重要课题。近年来&#xff0c;以“源网荷储一体化”为代表的智慧能源模式逐渐走进公众视野&#xff0c;尤其在与绿电直供相结合的零碳产业园区建设…

作者头像 李华
网站建设 2026/4/23 2:45:15

工业以太网设备中JLink仿真器烧写Flash操作指南

工业以太网设备中JLink烧写Flash实战指南&#xff1a;从连接到量产的完整路径 你有没有遇到过这样的场景&#xff1f; 产线上的工业网关突然“变砖”&#xff0c;固件更新失败&#xff1b;开发板连不上调试器&#xff0c;反复提示“Target not connected”&#xff1b;明明烧…

作者头像 李华