SpringBoot实现多数据源的切换实践

目录
  • 前言
  • 工程结构
  • 编码实现
    • yml文件
    • 主数据源MainDatasourceProperties
    • 其他数据源DynamicDatasourceProperties
    • 数据源配置类DatasourceConfiguration
    • DatasourceChooser
    • DatasourceContext
    • 数据源注解DatasourceScope
    • 数据源切面DynamicDatasourceAspect
  • 使用

前言

我们在进行软件开发的过程中,刚开始的时候因为无法估量系统后期的访问量和并发量,所以一开始会采用单体架构,后期如果网站流量变大, 并发量变大,那么就可能会将架构扩展为微服务架构,各个微服务对应一个数据库,不过这样的成本就有点大了,可能只是有些模块用的人比较多, 有些模块没什么人用,如果都进行服务拆分,其实也没那个必要,如果有些模块用的人比较多,那么我们可以采用读写分离来减轻压力,这样的话, 可以在一定程度上提升系统的用户体验,不过这只是在数据库的I/O上面做方案,如果系统的压力很大,那么肯定要做负载均衡,我们今天就先说 实现数据库的读写分离。我们要在代码层面实现数据库的读写分离,那么核心就是数据源的切换,本文基于AOP来实现数据源的切换。

工程结构

└─com
    └─steak
        └─transaction
            │  TransactionDemoApplication.java
            │
            ├─datasource
            │  │  DatasourceChooser.java
            │  │  DatasourceConfiguration.java
            │  │  DatasourceContext.java
            │  │  DatasourceScope.java
            │  │  DynamicDatasourceAspect.java
            │  │
            │  └─properties
            │          DynamicDatasourceProperties.java
            │          MainDatasourceProperties.java
            │
            ├─execute
            │      PlaceOrderExecute.java
            │
            ├─rest
            │      PlaceOrderApi.java
            │
            ├─result
            │      R.java
            │
            └─service
                    IntegralService.java
                    OrderService.java
                    StockService.java

在下面的实现中,我们一个有三个数据源,其中有一个是默认的,如果不指定具体的数据源,那么就使用默认的,我们是基于申明式的方式来切换 数据源,只需要在具体的接口上加上注解便能实现数据源的切换。

编码实现

yml文件

主数据源直接使用spring的配置,其他数据源采用自定义的方式,这里采用一个map结构来定义,方便进行解析,可以在yml文件里进行添加多个,在代码 逻辑层面不用改动。

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      username: root
      password: 123456
      url: jdbc:mysql://127.0.0.1:3306/db?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
      driver-class-name: com.mysql.cj.jdbc.Driver

dynamic:
  datasource: {
    slave1: {
      username: 'root',
      password: '123456',
      url: ' url: jdbc:mysql://127.0.0.1:3306/db?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC',
      driver-class-name: 'com.mysql.cj.jdbc.Driver'
    },
    slave2: {
      username: 'root',
      password: '123456',
      url: ' url: jdbc:mysql://127.0.0.1:3306/db?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC',
      driver-class-name: 'com.mysql.cj.jdbc.Driver'
    }
  }

主数据源MainDatasourceProperties

对于主数据源,我们单独拿出来放在一个类里面,不过其实也可以放到dynamic里面,只是需要做一定的处理,我们就简单的放几个连接属性。

/**
 * @author 刘牌
 * @date 2022/3/220:14
 */
@Component
@ConfigurationProperties(prefix = "spring.datasource.druid")
@Data
public class MainDatasourceProperties {
    private String username;
    private String password;
    private String url;
    private String driverClassName;
}

其他数据源DynamicDatasourceProperties

其他数据源使用一个Map来接受yml文件中的数据源配置

/**
 * @author 刘牌
 * @date 2022/3/213:47
 */
@Component
@ConfigurationProperties(prefix = "dynamic")
@Data
public class DynamicDatasourceProperties {
    private Map<String,Map<String,String>> datasource;
}

数据源配置类DatasourceConfiguration

配置类中主要对DataSource进行配置,主数据源我们按照正常的bean来定义连接属性,而其他数据数据源则使用反射的方式来进行连接属性的 配置,因为主数据源一般是不会变动的,但是其他数据源可能会发生变动,可能会添加,这时候如果通过硬编码取配置,那么每增加一个数据源,就需要 增加一个配置,显然不太行,所以就使用反射来进行赋值。

/**
 * @author 刘牌
 * @date 2022/3/111:34
 */
@Configuration
@AllArgsConstructor
public class DatasourceConfiguration {

    final MainDatasourceProperties mainDatasourceProperties;
    final DynamicDatasourceProperties dynamicDatasourceProperties;

    @Bean
    public DataSource datasource(){
        Map<Object,Object> datasourceMap = new HashMap<>();
        DatasourceChooser datasourceChooser = new DatasourceChooser();
        /**
         * main database
         */
        DruidDataSource mainDataSource = new DruidDataSource();
        mainDataSource.setUsername(mainDatasourceProperties.getUsername());
        mainDataSource.setPassword(mainDatasourceProperties.getPassword());
        mainDataSource.setUrl(mainDatasourceProperties.getUrl());
        mainDataSource.setDriverClassName(mainDatasourceProperties.getDriverClassName());
        datasourceMap.put("main",mainDataSource);
        /**
         * other database
         */
        Map<String, Map<String, String>> sourceMap = dynamicDatasourceProperties.getDatasource();
        sourceMap.forEach((datasourceName,datasourceMaps) -> {
            DruidDataSource dataSource = new DruidDataSource();
            datasourceMaps.forEach((K,V) -> {
                String setField = "set" + K.substring(0, 1).toUpperCase() + K.substring(1);
                //转换yml文件中带有-符号的属性
                String[] strings = setField.split("");
                StringBuilder newStr = new StringBuilder();
                for (int i = 0; i < strings.length; i++) {
                    if (strings[i].equals("-")) strings[i + 1] = strings[i + 1].toUpperCase();
                    if (!strings[i].equals("-")) newStr.append(strings[i]);
                }
                try {
                    DruidDataSource.class.getMethod(newStr.toString(),String.class).invoke(dataSource,V);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            datasourceMap.put(datasourceName,dataSource);
        });
        //设置目标数据源
        datasourceChooser.setTargetDataSources(datasourceMap);
        //设置默认数据源
        datasourceChooser.setDefaultTargetDataSource(mainDataSource);
        return datasourceChooser;
    }
}

上面使用数据源配置类中使用反射对其他数据源进行连接属性的设置,然后设置目标数据源和默认数据源,里面有一个DatasourceChooser

DatasourceChooser

DatasourceChooser继承自AbstractRoutingDataSourceAbstractRoutingDataSource可以实现数据源的切换,它里面的 determineCurrentLookupKey()方法需要我们返回一个数据源的名称,它会自动给我们匹配上数据源。

/**
 * @author 刘牌
 * @date 2022/3/112:21
 */
public class DatasourceChooser extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DatasourceContext.getDatasource();
    }
}

如下是AbstractRoutingDataSource的部分源码,我们可以看出数据源是一个Map结构,可以通过数据源名称查找到对应的数据源。

package org.springframework.jdbc.datasource.lookup;

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    @Nullable
    private Map<Object, DataSource> resolvedDataSources;
    public AbstractRoutingDataSource() {
    }
    protected DataSource determineTargetDataSource() {
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
    }

    @Nullable
    protected abstract Object determineCurrentLookupKey();
}

DatasourceContext

DatasourceContext内部是一个ThreadLocal,主要是用来存储每一个线程的数据源名称和获取数据源名称,而数据源的名称我们用过AOP切面 来获取。

/**
 * @author 刘牌
 * @date 2022/3/112:22
 */
public class DatasourceContext {
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static void setDatasource(String key){
        threadLocal.set(key);
    }
    public static String getDatasource(){
        return threadLocal.get();
    }
}

数据源注解DatasourceScope

DatasourceScope标准在方法上面,通过scope来指定数据源,不指定默认为主数据源main

/**
 * @author 刘牌
 * @date 2022/3/111:22
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DatasourceScope {
    String scope() default "main";
}

数据源切面DynamicDatasourceAspect

我们在访问每一个带有DatasourceScope注解的方法时,都会经过数据源切面DynamicDatasourceAspect,获取到注解上面的 scope的值后,通过DatasourceContext设置数据源名称,便可实现对数据源的切换。

/**
 * @author 刘牌
 * @date 2022/3/111:34
 */
@Aspect
@Component
public class DynamicDatasourceAspect {

    @Pointcut("@annotation(dataSourceScope)")
    public void dynamicPointcut(DatasourceScope dataSourceScope){}

    @Around(value = "dynamicPointcut(dataSourceScope)", argNames = "joinPoint,dataSourceScope")
    public Object dynamicAround(ProceedingJoinPoint joinPoint , DatasourceScope dataSourceScope) throws Throwable {
        String scope = dataSourceScope.scope();
        DatasourceContext.setDatasource(scope);
        return joinPoint.proceed();
    }
}

使用

只需要在具体的方法上面标注数据源注解@DatasourceScope,并指定scope的值,便可实现切换,如果不指定,那么就使用主数据源。

/**
 * @author 刘牌
 * @date 2022/3/19:49
 */
@Service
@AllArgsConstructor
public class OrderService {

    private JdbcTemplate jdbcTemplate;

    @DatasourceScope(scope = "slave1")
    public R saveOrder(Integer userId , Integer commodityId){
        String sql = "INSERT INTO `order`(user_id,commodity_id) VALUES("+userId+","+commodityId+")";
        jdbcTemplate.execute(sql);
        return R.builder().code(200).msg("save order success").build();
    }
}

到此这篇关于SpringBoot实现多数据源的切换实践的文章就介绍到这了,更多相关SpringBoot多数据源切换内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Springboot项目实现Mysql多数据源切换的完整实例

    一.分析AbstractRoutingDataSource抽象类源码 关注import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource以下变量 @Nullable private Map<Object, Object> targetDataSources; // 目标数据源 @Nullable private Object defaultTargetDataSource; // 默认目标数据源 @Null

  • springboot中mybatis多数据源动态切换实现

    目录 多数据源配置引入 动态数据源路由实现 动态数据源切换使用 案例源码 在开发中,动态数据源配置还是用的比较多的,比如在多数据源使用方面,又或者是在多个DB之间切换方面.这里给出一个动态数据源的配置方案,两个DB均以mysql为例. 多数据源配置引入 mybatis和mysql在springboot中的引入这里就不在说了,不了解的可以参见springboot中mysql与mybatis的引入. 数据源配置如下: datasource: master: type: com.alibaba.dru

  • SpringBoot +DynamicDataSource切换多数据源的全过程

    目录 固定多个数据源切换 1.由于我这个版本的自带DynamicDataSource包 2.在yml中配置两个数据源 3.启动时启动连接池的方法获取 4.载入各个连接池 5.dynamicDataSource类中 6.在切面中执行切换操作 刚开始用一个数据源,但是上头要求要做多数据源切换的.看了框架有自带的可以切数据源但是数据源是固定在YML文件的: 固定多个数据源切换 1.由于我这个版本的自带DynamicDataSource包 我就不引入了. 各位看官缺包自行去maven上查Dynamic-

  • springboot多数据源配置及切换的示例代码详解

    注:本文的多数据源配置及切换的实现方法是,在框架中封装,具体项目中配置及使用,也适用于多模块项目 配置文件数据源读取 通过springboot的Envioment和Binder对象进行读取,无需手动声明DataSource的Bean yml数据源配置格式如下: spring: datasource: master: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.cj.jdbc.Driver url:

  • SpringBoot AOP方式实现多数据源切换的方法

    最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据库的性能,考虑把这个查询走从库.所以涉及到需要在一个项目中配置多数据源,并且能够动态切换.经过一番摸索,完美实现动态切换,记录一下配置方法供大家参考. 设计总体思路 Spring-Boot+AOP方式实现多数据源切换,继承AbstractRoutingDataSource实现数据源动态的获取,在service层使用注解指定数据源. 步骤 一.多数据源配置 在applica

  • SpringBoot实现多数据源的切换实践

    目录 前言 工程结构 编码实现 yml文件 主数据源MainDatasourceProperties 其他数据源DynamicDatasourceProperties 数据源配置类DatasourceConfiguration DatasourceChooser DatasourceContext 数据源注解DatasourceScope 数据源切面DynamicDatasourceAspect 使用 前言 我们在进行软件开发的过程中,刚开始的时候因为无法估量系统后期的访问量和并发量,所以一开始

  • SpringBoot+Mybatis plus实现多数据源整合的实践

    SpringBoot 版本为1.5.10.RELEASE,Mybatis plus 版本为2.1.8. 第一步:填写配置信息: spring: aop: proxy-target-class: true auto: true datasource: druid: # 数据库 1 db1: url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zer

  • Springboot实现根据用户ID切换动态数据源

    首先在application.yml 文件添加一下配置  #每个库可连接最大用户数 dynamic-server: #每个服务最大建库数 database-max-number: 30 #每个库最大用户连接数 user-max-number: 200 template: gis_template 然后项目中添加 dynamicds 模块的代码,仅展示模块文件目录,代码太多不展示了 然后添加拦截器 @Override public void addInterceptors(InterceptorR

  • SpringBoot基于AbstractRoutingDataSource实现多数据源动态切换

    目录 一.场景 二.原理 三.代码示例 一.场景 在生产业务中,有一些任务执行了耗时较长的查询操作,在实时性要求不高的时候,我们希望将这些查询sql分离出来,去从库查询,以减少应用对主数据库的压力. 一种方案是在配置文件中配置多个数据源,然后通过配置类来获取数据源以及mapper相关的扫描配置,不同的数据源配置不佟的mapper扫描位置,然后需要哪一个数据源就注入哪一个mapper接口即可,这种方法比较简单.特征是通过mapper扫描位置区分数据源. 第二种方案是配置一个默认使用的数据源,然后定

  • springboot配置多数据源的一款框架(dynamic-datasource-spring-boot-starter)

    前言 前篇博客介绍了用基本的方式做多数据源,可以应对一般的情况,但是遇到一些复杂的情况就需要扩展下功能了,比如:动态增减数据源.数据源分组,纯粹多库,读写分离一主多从,从其他数据库或者配置中心读取数据源等等.其实就算没有这些需求,使用此款框架实现多数据源也比之前要便捷,快速的多 框架简介 dynamic-datasource-spring-boot-starter 是一个基于 springboot 的快速集成多数据源的启动器 文档:https://github.com/baomidou/dyna

  • 利用SpringBoot实现多数据源的两种方式总结

    目录 前言 基于dynamic-datasource实现多数据源 dynamic-datasource介绍 dynamic-datasource的相关约定 引入dynamic-datasource依赖 配置数据源 使用 @DS 切换数据源 基于AOP手动实现多数据源 项目工程结构 项目依赖 配置文件 自定义注解 编写DataSourceConstants 动态数据源名称上下文处理 获取当前动态数据源方法 动态数据源配置 AOP切面 编写TestUser实体 TestUserMapper Test

  • 关于Spring3 + Mybatis3整合时多数据源动态切换的问题

    以前的项目经历中,基本上都是spring + hibernate + Spring JDBC这种组合用的多.至于MyBatis,也就这个项目才开始试用,闲话不多说,进入正题. 以前的这种框架组合中,动态数据源切换可谓已经非常成熟了,网上也有非常多的博客介绍,都是继承AbstractRoutingDataSource,重写determineCurrentLookupKey()方法.具体做法就不在此废话了. 所以当项目中碰到这个问题,我几乎想都没有想,就采用了这种做法,但是一测试,一点反应都没有.当

  • Java自动化测试中多数据源的切换(实例讲解)

    在做自动化测试时,数据驱动是一个很重要的概念,当数据与脚本分离后,面对茫茫多的数据,管理数据又成了一个大问题,而数据源又可能面对多个,就跟在开发过程中,有时候要连接MYSQL,有时候又要连接SQL SERVER一样,如何做到快速切换?下面的示例中,我们将从一个数据源开始,一步步的演示下去: 一. 用外部文件做数据驱动的基本写法 1.1 我们在做数据驱动时,把数据存储在JAVA的属性文件中:data.properties username=test password=123456 1.2 解析pr

  • SpringBoot集成多数据源解析

    一,前面我们介绍了springboot的快速启动,大家肯定对springboot也有所了解,下面我们来介绍一下springboot怎么集成多数据源. 在有的项目开发中需要在一个项目中访问多个数据源或者两个项目之间通信(实质上是互相访问对方的数据库),在这里,我们介绍一下在一个项目中如何集成多个数据源(即访问多个不同的数据库),因为在项目中有时会有这种需求,比如在一个大型项目开发中,一个数据库中保存数据的索引,各种使用频繁的数据,另一个数据库中保存其他的数据. 1.下面我们来讨论一个问题,怎么集成

随机推荐