Spring Boot 动态数据源切换

背景

随着互联网应用的快速发展,多数据源的需求日益增多。Spring Boot 以其简洁的配置和强大的功能,成为实现动态数据源切换的理想选择。本文将通过具体的配置和代码示例,详细介绍如何在 Spring Boot 应用中实现动态数据源切换,帮助开发者高效应对不同业务场景下的数据管理需求。无论是读写分离还是数据隔离,都能轻松搞定。

AOP动态代理

AOP注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
    String name();
}

AOP切面类

@Aspect
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.example.aliyunai.db.TargetDataSource)")
    public void dataSourcePointcut() {
    }

    @Before("dataSourcePointcut()")
    public void changeDataSource(JoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        TargetDataSource targetDataSource = method.getAnnotation(TargetDataSource.class);
        if (targetDataSource != null) {
            String dataSourceName = targetDataSource.name();
            DataSourceContextHolder.setDataSourceKey(dataSourceName);
        }
    }

    @After("dataSourcePointcut()")
    public void clearDataSource(JoinPoint point) {
        DataSourceContextHolder.clearDataSourceKey();
    }
}

数据源配置

@Configuration
public class DataSourceConfig {


    @Bean(name = "master")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create()
                .type(HikariDataSource.class)
                .driverClassName("")
                .url("")
                .username("")
                .password("")
                .build();
    }

    @Bean(name = "slave")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create()
                .type(HikariDataSource.class)
                .driverClassName("")
                .url("")
                .username("")
                .password("")
                .build();
    }

    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dynamicDataSource(@Qualifier("master") DataSource master,
                                               @Qualifier("slave") DataSource slave) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", master);
        targetDataSources.put("slave", slave);

        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(master);
        return dynamicDataSource;
    }

}

线程上下文


public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSourceKey(String key) {
        contextHolder.set(key);
    }

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

    public static void clearDataSourceKey() {
        contextHolder.remove();
    }
}

动态数据源设置

public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);

    @Override
    protected Object determineCurrentLookupKey() {
        String dataSourceKey = DataSourceContextHolder.getDataSourceKey();
        logger.info("Determining current data source: {}", dataSourceKey);
        return dataSourceKey;
    }
}

service类

@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    @TargetDataSource(name = "master")
    public User queryFromPrimary() {
        User user = userMapper.selectById(1);
        return user;
    }

    @TargetDataSource(name = "slave")
    public User queryFromSecondary() {
        User user = userMapper.selectById(1L);
        return user;
    }
}

Mybatis 拦截器


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

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceInterceptor.class);

    // 假设这里有一个获取当前数据源标识的方法,你需要根据实际项目中的实现来替换
    private String getCurrentDataSourceKey() {
        // 示例:从某个上下文持有者中获取数据源标识,这里只是示意,实际要替换
        return DataSourceContextHolder.getDataSourceKey();
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1];
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        String sql = boundSql.getSql();
        //对单个表进行处理
        logger.info("sql:"+sql);
       
        String mappedStatementId = mappedStatement.getId();
        //对所有查询进行处理
        if (mappedStatementId.contains("query")) {
            String table = getTable(sql);
            if (table.equals("user")){
                DataSourceContextHolder.setDataSourceKey(getCurrentDataSourceKey());
            }
        //增删改
        }else if (mappedStatementId.contains("update")) {
            DataSourceContextHolder.setDataSourceKey(getCurrentDataSourceKey());
        }

        return invocation.proceed();
    }


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


    private static String getTable(String sql) {
        // 定义正则表达式模式,用于匹配 "from" 和 "where" 之间的表名
        Pattern pattern = Pattern.compile("FROM\\s+(\\w+)\\s+WHERE");
        Matcher matcher = pattern.matcher(sql);
        if (matcher.find()) {
            return matcher.group(1);
        }
        return null;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/925003.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

移远通信携手紫光展锐,以“5G+算力”共绘万物智联新蓝图

11月26日&#xff0c;2024紫光展锐全球合作伙伴大会在上海举办。作为紫光展锐重要的合作伙伴&#xff0c;移远通信应邀参会。 在下午的物联网生态论坛上&#xff0c;移远通信产品总监胡勇华作题为“5G与算力双擎驱动 引领智联新未来”的演讲&#xff0c;深度剖析了产业发展的趋…

Ubuntu 服务器部署 Tomcat 并配置 SSL/TLS 证书

本文目录 准备登陆云服务器安装 Java下载 tomcat 包配置防火墙浏览器访问 Tomcat 默认页面以服务的形式运行 Tomcat创建 Tomcat 用户和组创建 systemd 服务文件启动 tomcat 服务 Tomcat webapps 文件目录部署一个静态网站tomcat 的配置文件 将域名解析到服务器Tomcat 配置 SSL/…

【仓颉学习02】编译后运行报错:“由于找不到libcanjie-runtime.dll,无法继续执行代码。重新安装程序可能会解决此问题。”

敲了段《仓颉语言实践》&#xff08;张磊著&#xff09;书上的代码&#xff0c;如下&#xff1a; package test //coding:utf-8struct Employee{var name:Stringprivate var realSalary:Float64public Employee(name:String,realSalary:Float64){this.namenamethis.realSalary…

基于Linux操作系统的DNS服务器实验

实验6 DNS服务器 一、实验目的 熟练掌握DNS服务器的原理、配置过程与应用。 二、实验环境 硬件&#xff1a;PC电脑一台&#xff0c;网络正常。 配置&#xff1a;win10系统&#xff0c;内存大于8G &#xff0c;硬盘500G及以上。 软件&#xff1a;VMware、Ubuntu16.04。 三、…

Taro 鸿蒙技术内幕系列(三) - 多语言场景下的通用事件系统设计

基于 Taro 打造的京东鸿蒙 APP 已跟随鸿蒙 Next 系统公测&#xff0c;本系列文章将深入解析 Taro 如何实现使用 React 开发高性能鸿蒙应用的技术内幕 背景 在鸿蒙生态系统中&#xff0c;虽然原生应用通常基于 ArkTS 实现&#xff0c;但在实际研发过程中发现&#xff0c;使用 C…

万能门店小程序管理系统 onepic_uploade 任意文件上传漏洞复现

0x01 产品简介 万能门店小程序管理系统是一款功能强大的工具,旨在为各行业商家提供线上线下融合的全方位解决方案。是一个集成了会员管理和会员营销两大核心功能的综合性平台。它支持多行业使用,通过后台一键切换版本,满足不同行业商家的个性化需求。该系统采用轻量后台,搭…

STM32-C语言基础知识

C语言基础知识 stdint.h简介 给寄存器某个位赋值 给位6赋值为1流程&#xff1a;先清0&#xff0c;再赋值 带参数的宏定义 建议使用do {…}while(0)来构造宏定义 条件编译 条件编译后面必须跟宏语句&#xff0c;如#if _LED_H 指针使用常见的2大问题 1、未初始化 2、越界使…

在Unity中实现物体动画的完整流程

在Unity中&#xff0c;动画是游戏开发中不可或缺的一部分。无论是2D还是3D游戏&#xff0c;动画都能为游戏增添生动的视觉效果。本文将详细介绍如何在Unity中为物体添加动画&#xff0c;包括资源的准备、播放组件的添加、动画控制器的创建以及动画片段的制作与调度。 1. 准备动…

windows安装itop

本文介绍 win10 安装 itop 安装WAMP集成环境前 先安装visual c 安装itop前需要安装WAMP集成环境(windowsApacheMysqlPHP) 所需文件百度云盘 通过网盘分享的文件&#xff1a;itop.zip 链接: https://pan.baidu.com/s/1D5HrKdbyEaYBZ8_IebDQxQ 提取码: m9fh 步骤一&#xff1…

【Linux打怪升级记 | 报错02】-bash: 警告:setlocale: LC_TIME: 无法改变区域选项 (zh_CN.UTF-8)

&#x1f5fa;️博客地图 &#x1f4cd;1、报错发现 &#x1f4cd;2、原因分析 &#x1f4cd;3、解决办法 &#x1f4cd;4、测试结果 1、报错发现 装好了CentOS操作系统&#xff0c;使用ssh远程登陆CentOS&#xff0c;出现如下告警信息&#xff1a; bash: 警告:setlocale…

前端-Git

一.基本概念 Git版本控制系统时一个分布式系统&#xff0c;是用来保存工程源代码历史状态的命令行工具 简单来说Git的作用就是版本管理工具。 Git的应用场景&#xff1a;多人开发管理代码&#xff1b;异地开发&#xff0c;版本管理&#xff0c;版本回滚。 Git 的三个区域&a…

原生html+css+ajax+php图片压缩后替换原input=file上传

当前大部分照片尺寸大于5MB&#xff0c;而50MB限制的PHP通常上传4MB左右 于是就需要压缩后上传&#xff0c;上5代码使用后筛选的代码 <?php if ($_SERVER[REQUEST_METHOD] POST) { $uploadDir uploads/ . date(Ymd) . /; if (!is_dir($uploadDir)) { mkdir($uploadDir, …

k8s集群部署metrics-server

1、Metrics Server介绍 Metrics Server 是集群级别的资源利用率数据的聚合器。从 Kubelets收集资源指标&#xff0c;并通过 Metrics API 在 Kubernetes apiserver 中公开它们&#xff0c;以供 Horizontal Pod Autoscaler 和Vertical Pod Autoscaler 使用。 Metrics API 也可以…

yolov5的pt模型转化为rk3588的rknn,并在rk3588上调用api进行前向推理

当使用yolov5进行目标检测且进行边缘计算的场景时&#xff0c;要考虑性价比或者国产化的话&#xff0c;rk3588板子是个不错的选择。 本篇介绍yolov5的pytorch模型转化为rknn的流程&#xff0c;并展示在rk板子上如何调用相关api来使用转好的rknn模型进行前向推理。 pt转rknn流程…

家校通小程序实战教程03学生管理

目录 1 创建数据源2 搭建后台功能3 设置主列字段4 批量导入数据5 设置查询条件6 实现查询和重置总结 我们现在已经搭建了班级管理&#xff0c;并且录入了班级口令。之后就是加入班级的功能了。这里分为老师加入班级和学生家长加入班级。 如果是学生家长的话&#xff0c;在加入之…

题目 3209: 蓝桥杯2024年第十五届省赛真题-好数

一个整数如果按从低位到高位的顺序&#xff0c;奇数位&#xff08;个位、百位、万位 &#xff09;上的数字是奇数&#xff0c;偶数位&#xff08;十位、千位、十万位 &#xff09;上的数字是偶数&#xff0c;我们就称之为“好数”。给定一个正整数 N&#xff0c;请计算从…

ASP.NET Core Web API 控制器

文章目录 一、基类&#xff1a;ControllerBase二、API 控制器类属性三、使用 Get() 方法提供天气预报结果 在深入探讨如何编写自己的 PizzaController 类之前&#xff0c;让我们先看一下 WeatherController 示例中的代码&#xff0c;了解它的工作原理。 在本单元中&#xff0c;…

scrapy豆瓣爬虫增强-批量随机请求头

1.1 豆瓣爬虫增强,中间件随机请求头 1.2 清除原有的中间件,进行中间件测试 1.3 导入全新的中间件 1.4 运行爬虫,这个时候的请求头是固定的 1.5 强化对agent的输出,会舍弃输出cookie,使输出更明了 1.6 转移输出请求头位置 新增输出 造成这样问题的原因是Douban/Douban/settings…

三维地形图计算软件(三)-原基于PYQT5+pyqtgraph旧代码

最先入手设计三维地形图及平基挖填方计算软件时&#xff0c;地形图的显示方案是&#xff1a;三维视图基于pyqtgraph.opengl显示和二维视图基于pyqtgraph的PlotWidget来显示地形地貌&#xff0c;作到一半时就发现&#xff0c;地形点过多时&#xff0c;将会造成系统卡顿(加载时主…

累积局部效应 (ALE) 图分析记录

Git地址&#xff1a;https://github.com/blent-ai/ALEPython/tree/dev 查看源码需要pip install alepython安装&#xff0c;这边查看源码发现就实际就一个py文件而已&#xff0c;我懒得再去安装&#xff0c;故直接下载源码&#xff0c;调用方法也可&#xff1b; # -*- coding:…