SpringBoot动态数据源配置

序:数据源动态切换流程图如下:

1:pom.xml文件依赖声明

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>2.1.4</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.12</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2:application.yml配置文件

配置多个数据源信息

server:
port: 9000

dynamic:
datasource:
  ds1: # 数据源1
    jdbc-url: jdbc:mysql://localhost:3306/test1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  ds2: # 数据源2
    jdbc-url: jdbc:mysql://localhost:3306/test2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
  map-underscore-to-camel-case: true

3:数据源的配置到Spring管理

/**
* description 数据源配置类
*
* @author liekkas 2021/08/21 12:33
*/
@Configuration
public class DynamicDataSourceConfig {

  @Bean(name = "dataSource1")
  @ConfigurationProperties(prefix = "dynamic.datasource.ds1")
  public DataSource dateSource1() {
      return DataSourceBuilder.create().build();
  }

  @Bean(name = "dataSource2")
  @ConfigurationProperties(prefix = "dynamic.datasource.ds2")
  public DataSource dateSource2() {
      return DataSourceBuilder.create().build();
  }

  @Bean
  @Primary
  public DynamicRoutingDataSource dynamicDataSource() {
      DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();

//设置默认的数据源
      dynamicRoutingDataSource.setDefaultTargetDataSource(dateSource1());
      Map<Object, Object> targetDataSources = new HashMap<>(4);

      targetDataSources.put("dataSource1", dateSource1());
      targetDataSources.put("dataSource2", dateSource2());
//设置数据源
      dynamicRoutingDataSource.setTargetDataSources(targetDataSources);
      dynamicRoutingDataSource.afterPropertiesSet();
      DynamicDataSourceContextHolder.dataSourceSet.addAll(targetDataSources.keySet());
      return dynamicRoutingDataSource;
  }
}

注意:@ConfigurationProperties注解的prefix的值和配置文件application.yml必须要保持一致

3:配置数据源上下文

我们需要新建一个数据源上下文,用户记录当前线程使用的数据源是什么,以及记录所有注册成功的数据源的集合。对于线程级别的私有变量,我们首先ThreadLocal来实现。

/**
* description 设置当前线程数据源上下文类
*
* @author liekkas 2021/08/21 12:37
*/
public class DynamicDataSourceContextHolder {
  private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);

  /**
    * 存储已经注册的数据源的key
    */
  public static Set<Object> dataSourceSet = new HashSet<>();

  /**
    * 线程级别的私有变量
    */
  private static final ThreadLocal<String> HOLDER = new ThreadLocal<>();

  public static String getDataSourceRouterKey() {
      return HOLDER.get();
  }

  public static void setDataSourceRouterKey(String dataSourceRouterKey) {
      logger.info("当前数据源正在切换中......");
      HOLDER.set(dataSourceRouterKey);
  }

  /**
    * 设置数据源之前一定要先移除
    */
  public static void removeDataSourceRouterKey() {
      HOLDER.remove();
  }

  /**
    * 判断指定dataSource当前是否存在
    *
    * @param dataSource dataSource
    * @return boolean
    */
  public static boolean isExistDataSource(Object dataSource) {
      return dataSourceSet.contains(dataSource);
  }

}

4:动态数据源路由

第三步我们以及新建了数据源上下文,用于存储我们当前线程的数据源,那么怎么通知spring用当前的数据源,spring提供一个接口,名为AbstractRoutingDataSource的抽象类,我们只需要重写determineCurrentLookupKey方法就可以,这个方法看名字就知道,就是返回当前线程的数据源,那我们只需要从我们刚刚的数据源上下文中取出当前数据源即可

/**
* description 数据源动态路由
*
* @author liekkas 2021/08/21 12:39
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

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

  @Override
  protected Object determineCurrentLookupKey() {
      String dataSourceName = DynamicDataSourceContextHolder.getDataSourceRouterKey();
      logger.info("当前数据源是:{}", dataSourceName);
      return DynamicDataSourceContextHolder.getDataSourceRouterKey();
  }
}:

5:数据源动态切换注解

/**
* description 动态切换数据源注解
* 该注解可以同时作用于类和方法上,如果类和方法注解同时存在,则方法的注解优先级高于类上的注解优先级
* 也就是说类和方法同时存在该注解的话,则会使用方法的数据源
*
* @author liekkas 2021/08/21 12:40
*/
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
  String value() default "dataSource1";
}:

6:利用aop实现数据源动态切换

/**
* description 动态切换数据源切面
*
* @author liekkas 2021/08/21 12:42
*/
@Aspect
@Component
@Order(-1)
public class DynamicDataSourceAspect {
  private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

  /**
    * 同时拦截标注DataSource的注解类上或者方法
    */
  @Pointcut(value = "@within(com.liekkas.config.datasource.DataSource) || @annotation(com.liekkas.config.datasource.DataSource)")
  public void datasource() {
  }


  @Before("datasource()")
  public void changeDataSource(JoinPoint joinPoint) {

      Class<?> klass = joinPoint.getTarget().getClass();

      MethodSignature signature = (MethodSignature) joinPoint.getSignature();
      Method method = signature.getMethod();

      DataSource dataSource;
      if (method.isAnnotationPresent(DataSource.class)) {
          dataSource = method.getAnnotation(DataSource.class);
      } else {
          dataSource = klass.getAnnotation(DataSource.class);
      }

      //判断注解中的数据源是否在已注册的数据源中
      if (DynamicDataSourceContextHolder.isExistDataSource(dataSource.value())) {
          DynamicDataSourceContextHolder.setDataSourceRouterKey(dataSource.value());
          logger.info("数据源设置为 > {}", dataSource.value());
      } else {
          throw new RuntimeException("数据源[" + dataSource.value() + "]不存在");
      }
  }

  @After("datasource()")
  public void restoreDataSource() {
      DynamicDataSourceContextHolder.removeDataSourceRouterKey();
  }
}

7:测试

7.1:controller

/**
* description
*
* @author liekkas 2021/01/09 15:47
*/
@RestController
public class PersonController {

  @Resource
  private PersonService personService;

  @GetMapping("person")
  public List<Person> findPerson() {
      return personService.findPerson();
  }

  @GetMapping("student")
  public List<Student> findStudent() {
      return personService.findStudent();
  }
}

7.2: service

public interface PersonService {
  /**
    * findPerson
    *
    * @return list
    */
  List<Person> findPerson();
  /**
    * findStudent
    *
    * @return list
    */
  List<Student> findStudent();
}

7.3: serviceImpl

@Service
@DataSource("dataSource1")
public class PersonServiceImpl implements PersonService {

  @Resource
  private PersonMapper personMapper;

  @Override
  public List<Person> findPerson() {
      return personMapper.findPerson();
  }

  @Override
  @DataSource("dataSource2")
  public List<Student> findStudent() {
      return personMapper.findStudent();
  }
}

由于篇幅关系,mapper以及实体类不再描述

测试接口调用

http://localhost:9000/person

http://localhost:9000/student

至此数据源动态切换已经完成。

小结:数据源动态切换主要原理就是实现了Spring提供的AbstractRoutingDataSource类,然后我们就可以根据自己的需要利用aop和自定义注解,ThreadLocal来实现动态切换使用哪一个数据源了。

SpringBoot动态数据源配置的更多相关文章

  1. Springboot+Druid 动态数据源配置监控

    一.引入maven依赖,使用 starter 与原生 druid 依赖配置有所不同 <dependency> <groupId>com.alibaba</groupId& ...

  2. SpringBoot动态数据源

    1.原理图 2.创建枚举类 /** * 存数据源key值 */ public enum DataSourceKey { master,salve,migration } 3.创建自定义注解类 /** ...

  3. Druid动态数据源配置

    上文已经讲了单个数据源的Druid的配置(http://www.cnblogs.com/nbfujx/p/7686634.html) Druid动态数据源配置 主要是继承AbstractRouting ...

  4. Spring Boot2.x 动态数据源配置

    原文链接: Spring Boot2.x 动态数据源配置 基于 Spring Boot 2.x.Spring Data JPA.druid.mysql 的动态数据源配置Demo,适合用于数据库的读写分 ...

  5. Springboot 多数据源配置,结合tk-mybatis

    一.前言 作为一个资深的CRUD工程师,我们在实际使用springboot开发项目的时候,难免会遇到同时使用多个数据库的情况,比如前脚刚查询mysql,后脚就要查询sqlserver. 这时,我们很直 ...

  6. Spring-Boot 多数据源配置+动态数据源切换+多数据源事物配置实现主从数据库存储分离

    一.基础介绍 多数据源字面意思,比如说二个数据库,甚至不同类型的数据库.在用SpringBoot开发项目时,随着业务量的扩大,我们通常会进行数据库拆分或是引入其他数据库,从而我们需要配置多个数据源. ...

  7. Springboot多数据源配置--数据源动态切换

    在上一篇我们介绍了多数据源,但是我们会发现在实际中我们很少直接获取数据源对象进行操作,我们常用的是jdbcTemplate或者是jpa进行操作数据库.那么这一节我们将要介绍怎么进行多数据源动态切换.添 ...

  8. springboot+多数据源配置

    作者:纯洁的微笑 出处:http://www.ityouknow.com/ 起多数据源,一般都来解决那些问题呢,主从模式或者业务比较复杂需要连接不同的分库来支持业务.我们项目是后者的模式,网上找了很多 ...

  9. Spring Boot + Mybatis多数据源和动态数据源配置

    文章转自 https://blog.csdn.net/neosmith/article/details/61202084 网上的文章基本上都是只有多数据源或只有动态数据源,而最近的项目需要同时使用两种 ...

  10. 基于注解实现SpringBoot多数据源配置

    1.功能介绍 在实际的开发中,同一个项目中使用多个数据源是很常见的场景.最近在学习的过程中使用注解的方式实现了一个Springboot项目多数据源的功能.具体实现方式如下. 2.在applicatio ...

随机推荐

  1. Cron Job 表达式解析

    Cron Job 表达式解析 Redisant Toolbox 是一款面向开发者的多合一工具箱,超过30种常用的开发工具:精心设计,快速.高效:离线使用,尊重您的隐私. 目录 Cron Job 表达式 ...

  2. golang 常用操作

    golang 获取切片 slice 第一个 最后一个 元素 复合数据类型切片通常用作Go中索引数据的口语结构. 该类型[]intSlice是具有类型为integer的元素的切片. len函数用于获取切 ...

  3. 网络协议分析与抓包 TCP/IP UDP等

    学习地址: https://www.bilibili.com/video/BV1hV411U74y?p=4 https://www.bilibili.com/video/BV1S7411R7kF?p= ...

  4. Oracle修改字段长度及属性

    首发微信公众号:SQL数据库运维 原文链接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247486117&idx=1 ...

  5. python教程3.2:字典

    字典相比较列表,优势:查找key的需求,列表需要遍历,字典查找速度很快,很方便, 定义  特性 查找.增加和修改操作  删除操作 循环操作  全局函数

  6. 效率工具mysoV0.3.0功能介绍

      也许大家在日常工作中,有使用过提升效率的工具,比如快速搜索文件的Everything:火柴:Listary等,这些工具都在一定程度上提升您的工作效率.myso作为后来者,跟上述软件一样,也是为提升 ...

  7. C 语言编程 — 高级数据类型 — void 类型

    目录 文章目录 目录 前文列表 void 类型 前文列表 <程序编译流程与 GCC 编译器> <C 语言编程 - 基本语法> <C 语言编程 - 基本数据类型> & ...

  8. Django信号与扩展:深入理解与实践

    title: Django信号与扩展:深入理解与实践 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 后端开发 tag ...

  9. Java中双括号初始化是个什么操作

    最近在阅读Mybatis源码的时候,看到了一种原来很少见到的语法: public class RichType { ... private List richList = new ArrayList( ...

  10. Asp.Net 单点登录(SSO)|禁止重复登陆|登录强制下线

    背景: 先上个图,看一下效果: SSO英文全称Single Sign On(单点登录).SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.它包括可以将这次主要的登录映射到其 ...