Springboot+aop实现配置多数据源

news2024/12/4 1:32:15

springboot+Aop实现动态数据源配置

文章目录

  • springboot+Aop实现动态数据源配置
    • 前言
    • 1、添加依赖(本项目是一个springboot项目)
    • 2、定义数据库类型枚举类(enum)
    • 3、定义注解(annotation)
    • 4、编写ThreadLocal存储数据上下文信息
    • 5、获取数据源信息
    • 6、自定义切面(Aspect)
    • 7、配置数据源信息
    • 8、编写配置文件

前言

​ 这是我司在对于动态数据源的一个处理方法,采用动态加载数据库信息实现动态数据源的切换。通过使用Spring AOP + 注解来替换当前线程ThreadLocal中的值,并且通过重写AbstractRoutingDataSource类重写determineCurrentLookUpKey()方法,实现动态数据源切换,满足功能实现的代码0侵入性,并且高度解耦,实现可拔插功能效果

1、添加依赖(本项目是一个springboot项目)

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-core</artifactId>
    <version>3.5.1</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.9</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.22</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.32</version>
    <scope>runtime</scope>
</dependency>

2、定义数据库类型枚举类(enum)

/**
 * 数据库配置名字字段
 * @author zh
 * @data 17点14分
 *
 */
public enum DBTypeEnum {
    cs1db("cs1-db"), cs2db("cs2-db");

    private String value;

    DBTypeEnum(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

枚举主要是用于简化if-else语句。

3、定义注解(annotation)


/**
 * @author zh
 * @data 17点18分
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DataSourceSwitch {
    /****
     * 顺便设置下默认数据源
     * @return
     */
    DBTypeEnum value() default DBTypeEnum.cs1db;
}

定义DataSourceSwitch注解,默认数据源为cs1db(数据库一)。

4、编写ThreadLocal存储数据上下文信息


/**
 * @author zh
 */
public class DbContextHolder {
    private static final ThreadLocal contextHolder = new ThreadLocal<>();
    /**
     * 设置数据源
     * @param dbTypeEnum
     */
    public static void setDbType(DBTypeEnum dbTypeEnum) {
        contextHolder.set(dbTypeEnum.getValue());
    }

    /**
     * 取得当前数据源
     * @return
     */
    public static String getDbType() {
        return (String) contextHolder.get();
    }

    /**
     * 清除上下文数据
     */
    public static void clearDbType() {
        contextHolder.remove();
    }
}

工具类DbContextHolder用于存储数据名上下文。

5、获取数据源信息

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @author  zh
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * 取得当前使用哪个数据源
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DbContextHolder.getDbType();
    }
}

工具类DynamicDataSource继承AbstractRoutingDataSource重写determineCurrentLookupKey()方法,从工具类DbContextHolder中获取当前数据源信息。

6、自定义切面(Aspect)

/**
 *  @author zh
 *  数据源AOP注解实现
 */
@Component
@Aspect
@Order(-100)
public class DataSourceSwitchAspect {

    private Logger log= LoggerFactory.getLogger(DataSourceSwitchAspect.class);

    @Pointcut("execution(* com.zh.cn.business.cs1db..*.*(..))")
    private void cs1dbAspect() {
    }

    @Pointcut("execution(* com.zh.cn.business.cs2db..*.*(..))")
    private void cs2dbAspect() {
    }
    
    @Before( "cs1dbAspect()" )
    public void basic(JoinPoint joinPoint) {
        //log.info("切换到cs1db 数据源...");
        setDataSource(joinPoint, DBTypeEnum.cs1db);
    }

    @Before("cs2dbAspect()" )
    public void order (JoinPoint joinPoint) {
        //log.info("切换到cs2db 数据源...");
        setDataSource(joinPoint,DBTypeEnum.cs2db);
    }

    /**
     * 添加注解方式,如果有注解优先注解,没有则按传过来的数据源配置
     * @param joinPoint
     * @param dbTypeEnum
     */
    private void setDataSource(JoinPoint joinPoint, DBTypeEnum dbTypeEnum) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        DataSourceSwitch dataSourceSwitch = methodSignature.getMethod().getAnnotation(DataSourceSwitch.class);
        if (Objects.isNull(dataSourceSwitch) || Objects.isNull(dataSourceSwitch.value())) {
            DbContextHolder.setDbType(dbTypeEnum);
        }else{
           // log.info("根据注解来切换数据源,注解值为:"+dataSourceSwitch.value());
            switch (dataSourceSwitch.value().getValue()) {
                case "cs1-db":
                    DbContextHolder.setDbType(DBTypeEnum.cs1db);
                    break;
                case "cs2-db":
                    DbContextHolder.setDbType(DBTypeEnum.cs2db);
                    break;
                default:
                    DbContextHolder.setDbType(dbTypeEnum);
            }
        }
    }
}

通过springAop自定义切面,切入点在* com.zh.cn.business.cs2db..*.*(..)),表示切入点在com.zh.cn.business.cs2db包下的任意子包、任意方法和任意返回值,并通过setDataSource()方法设置工具类DbContextHolder中数据源信息。

7、配置数据源信息

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.baomidou.mybatisplus.generator.config.rules.DbType;
import com.zh.cn.Utils.DynamicDataSource;
import com.zh.cn.constants.DBTypeEnum;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 加载DataSource
 */
@Configuration
@MapperScan({"com.zh.cn.**.mapper"})
public class MybatisPlusConfig {

    /**
     * mapper-plus分页插件<br>
     * 文档:http://mp.baomidou.com<br>
     */
   /* @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        //paginationInterceptor.setLocalPage(true);// 开启 PageHelper 的支持
        return paginationInterceptor;
    }*/
   @Bean
   public MybatisPlusInterceptor mybatisPlusInterceptor() {
      MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
      interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
      //乐观锁
      interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
      return interceptor;
   }


    @Bean(name = "cs1db")
    @ConfigurationProperties(prefix = "spring.datasource.druid.cs1-db" )
    public DataSource cs1db () {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "cs2db")
    @ConfigurationProperties(prefix = "spring.datasource.druid.cs2-db" )
    public DataSource cs2db () {
        return DruidDataSourceBuilder.create().build();
    }

   /**
     * 动态数据源配置
     * @return
     */
    @Bean
    @Primary
    public DataSource multipleDataSource (@Qualifier("cs1db") DataSource cs1db,
                                          @Qualifier("cs2db") DataSource cs2db){
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map< Object, Object > targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.cs1db.getValue(), cs1db );
        targetDataSources.put(DBTypeEnum.cs2db.getValue(), cs2db);

        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(cs1db);//指定默认
        return dynamicDataSource;
    }

    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(multipleDataSource(cs1db(),cs2db()));

        sqlSessionFactory.setMapperLocations(new 	PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*/*Mapper.xml"));

        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(false);
        sqlSessionFactory.setConfiguration(configuration);
        sqlSessionFactory.setPlugins(new Interceptor[]{
            mybatisPlusInterceptor()
        });
        return sqlSessionFactory.getObject();
    }


}

通过覆盖默认的DataSource进行加载动态数据源的配置信息,通过**multipleDataSource()方法设置数据源,最后通过sqlSessionFactory()**设置数据源的最终加载

8、编写配置文件

spring:
  datasource:
    druid:
	      type: com.alibaba.druid.pool.DruidDataSource
	      cs1-db:
	        driverClassName: com.mysql.cj.jdbc.Driver
	        url: jdbc:mysql://127.0.0.1:3306/short-chain?useUnicode=true&characterEncoding=UTF-8
	        username: root
	        password: 
	        initialSize: 10
	        minIdle: 20
	        maxActive: 100
	        maxWait: 60000
	      cs2-db:
	        driverClassName: com.mysql.cj.jdbc.Driver
	        url: jdbc:mysql://127.0.0.1:3306/short-chain?useUnicode=true&characterEncoding=UTF-8
	        username: root
	        password: 
	        initialSize: 10
	        minIdle: 20
	        maxActive: 100
	        maxWait: 60000
	      	maxActive: 100
	        maxWait: 60000
      

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IPv6 NDP 记录

NDP&#xff08;Neighbor Discovery Protocol&#xff0c;邻居发现协议&#xff09; 是 IPv6 的一个关键协议&#xff0c;它组合了 IPv4 中的 ARP、ICMP 路由器发现和 ICMP 重定向等协议&#xff0c;并对它们作出了改进。该协议使用 ICMPv6 协议实现&#xff0c;作为 IPv6 的基…

个人全栈开发微信小程序上线了(记日记)

个人开发的全栈项目&#xff0c;《每日记鸭》微信小程序上线了&#xff01; 主要是技术栈&#xff1a;uniapp,koa2,mongodb,langchian&#xff1b; 感兴趣的小伙伴可以来捧捧场&#xff01;

用源码编译虚幻引擎,并打包到安卓平台

用源码编译虚幻引擎&#xff0c;并打包到安卓平台 前往我的博客,获取更优的阅读体验 作业内容: 源码编译UE5.4构建C项目&#xff0c;简单设置打包到安卓平台 编译虚幻 5 前置内容 这里需要将 Epic 账号和 Github 账号绑定&#xff0c;然后加入 Epic 邀请的组织&#xff0c…

如何更改手机GPS定位

你是否曾想过更改手机GPS位置以保护隐私、玩游戏或访问受地理限制的内容&#xff1f;接下来我将向你展示如何使用 MagFone Location Changer 更改手机GPS 位置&#xff01;无论是在玩Pokmon GO游戏、发布社媒贴子&#xff0c;这种方法都快速、简单且有效。 第一步&#xff1a;下…

如何在 Ubuntu 上使用 Docker 部署 LibreOffice Online

简介 LibreOffice Online&#xff08;也称为Collabora Online&#xff09;是一个开源的在线办公套件&#xff0c;它提供了与LibreOffice桌面版相似的功能&#xff0c;但完全在云端运行。这意味着用户可以通过浏览器访问和编辑文档&#xff0c;而无需在本地计算机上安装任何软件…

Uniapp运行环境判断和解决跨端兼容性详解

Uniapp运行环境判断和解决跨端兼容性 开发环境和生产环境 uniapp可通过process.env.NODE_ENV判断当前环境是开发环境还是生产环境&#xff0c;一般用于链接测试服务器或者生产服务器的动态切换。在HX中&#xff0c;点击运行编译出来的代码是开发环境&#xff0c;点击发行编译…

华为Ensp模拟器配置OSPF路由协议

目录 简介 实验步骤 Pc配置 路由器配置 OSPF配置 交换机配置 简介 开放式最短路径优先 (OSPF) 协议深度解析 简介 开放式最短路径优先&#xff08;Open Shortest Path First, OSPF&#xff09;是一种内部网关协议&#xff08;IGP&#xff09;&#xff0c;用于在自治系统…

【JavaEE初阶 — 多线程】线程池

目录 1. 线程池的原理 1.1 为什么要有线程池 1.2 线程池的构造方法 1.3 线程池的核心参数 1.4 TimeUnit 1.5 工作队列的类型 1.6 工厂设计模式 1.6.1 工厂模式概念 1.6.2 使用工厂模式的好处 1.6.3 使用工厂模式的典型案例 1.6.4 Thread…

Vue通过file控件上传文件到Node服务器

功能&#xff1a; 1.多文件同时上传、2.拖动上传、3.实时上传进度条、4.中断上传和删除文件、5.原生file控件的美化 搁置的功能: 上传文件夹、大文件切片上传、以及其他限制条件未处理 Node服务器的前置准备: 新建文件夹: file_upload_serve初始化npm: npm …

Spring Security使用基本认证(Basic Auth)保护REST API

基本认证概述 基本认证&#xff08;Basic Auth&#xff09;是保护REST API最简单的方式之一。它通过在HTTP请求头中携带Base64编码过的用户名和密码来进行身份验证。由于基本认证不使用cookie&#xff0c;因此没有会话或用户登出的概念&#xff0c;这意味着每次请求都必须包含…

[大数据] Iceberg

G:\Bigdata\25.iceberg 第3章 与 Hive集成 3.1 环境准备 1)Hive与Iceberg的版本对应关系如下 Hive 版本 官方推荐Hive版本 Iceberg 版本 2.x 2.3.8 0.8.0-incubating – 1.1.0 3.x 3.1.2 0.10.0 – 1.1.0 Iceberg与Hive 2和Hive 3.1.2/3的集成,支持以下特性: 创建表删除表…

JMeter监听器与压测监控之Grafana

Grafana 是一个开源的度量分析和可视化套件&#xff0c;通常用于监控和观察系统和应用的性能。本文将指导你如何在 Kali Linux 上使用 Docker 来部署 Grafana 性能监控平台。 前提条件 Kali Linux&#xff1a;确保你已经安装了 Kali Linux。Docker&#xff1a;确保你的系统已…

C/C++ 优化,strlen 示例

目录 C/C optimization, the strlen examplehttps://hallowed-blinker-3ca.notion.site/C-C-optimization-the-strlen-example-108719425da080338d94c79add2bb372 揭开优化的神秘面纱... 让我们来谈谈 CPU 等等&#xff0c;SIMD 是什么&#xff1f; 为什么 strlen 是一个很…

【Linux学习】【Ubuntu入门】1-8 ubuntu下压缩与解压缩

1.Linux系统下常用的压缩格式 常用的压缩扩展名&#xff1a;.tar、.tar.bz2、.tar.gz 2.Windows下7ZIP软件安装 Linux系统下很多文件是.bz2&#xff0c;.gz结尾的压缩文件。 3.Linux系统下gzip压缩工具 gzip工具负责压缩和解压缩.gz格式的压缩包。 gzip对单个文件进行…

【Linux网络编程】简单的UDP套接字

目录 一&#xff0c;socket编程的相关说明 1-1&#xff0c;sockaddr结构体 1-2&#xff0c;Socket API 二&#xff0c;基于Udp协议的简单通信 三&#xff0c;UDP套接字的应用 3-1&#xff0c;实现英译汉字典 一&#xff0c;socket编程的相关说明 Socket编程是一种网络通信…

【工控】线扫相机小结 第三篇

海康软件更新 目前使用的是 MVS_STD_4.3.2_240705.exe &#xff0c;最新的已经到4.4了。 一个大的变动 在上一篇中我们提到一个问题&#xff1a; 需要注意的是&#xff0c;我们必须先设置 TriggerSelector 是 “FrameBurstStart” 还是 “LineStart” 再设置TriggerMode 是 …

Java基础知识(五)

文章目录 ObjectObject 类的常见方法有哪些&#xff1f; 和 equals() 的区别hashCode() 有什么用&#xff1f;为什么要有 hashCode&#xff1f;为什么重写 equals() 时必须重写 hashCode() 方法&#xff1f; 参考链接 Object Object 类的常见方法有哪些&#xff1f; Object 类…

[高阶数据结构(一)]并查集详解

1.前言 本系列会带大家走进高阶数据结构的学习, 其中包括并查集,图论, LRU cache, B树, B树, B*树, 跳表. 其中, 图论中讲解的时间最长, 包括邻接表, 邻接矩阵, 广度优先遍历, 深度优先遍历, 最小生成树中的kruskal算法以及prim算法&#xff1b;最短路径中的dijkstra算法, bell…

应聘美容师要注意什么?博弈美业收银系统/管理系统/拓客系统分享建议

随着美容行业的不断发展&#xff0c;成为一名优秀的美容师需要具备一系列重要的技能和品质。无论是在面试过程中还是在实际工作中&#xff0c;以下建议将帮助你在应聘美容师职位时脱颖而出&#xff1a; ▶ 专业技能和资格 首先&#xff0c;确保你具备所需的专业技能和资格。这…

el-cascader 使用笔记

1.效果 2.官网 https://element.eleme.cn/#/zh-CN/component/cascader 3.动态加载&#xff08;官网&#xff09; <el-cascader :props"props"></el-cascader><script>let id 0;export default {data() {return {props: {lazy: true,lazyLoad (…