spring实现动态切换、添加数据源及源码分析

下面是关于“spring实现动态切换、添加数据源及源码分析”的完整攻略。

1. 动态添加数据源

1.1 添加数据源配置

在Spring Boot的配置文件中,以 spring.datasource. 开头的配置项表示数据源相关的配置,可以在程序启动时从配置文件中读取。

接下来,我们来实现动态向配置中添加用户自定义的数据源。

首先,在 application.properties 或 application.yml 中定义可扩展的数据源配置项:

spring.datasource.dynamic.datasource.master.url= jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.dynamic.datasource.master.username=root
spring.datasource.dynamic.datasource.master.password=123456
spring.datasource.dynamic.datasource.slave.url= jdbc:mysql://localhost:3307/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.dynamic.datasource.slave.username=root
spring.datasource.dynamic.datasource.slave.password=123456

1.2 添加数据源切换器

这里我们使用 Spring 在 AOP 技术实现数据源的动态切换。具体做法是,定义一个切换数据源的切面,然后将其织入到我们需要切换数据源的方法上。

@Aspect
@Component
public class DynamicDataSourceAspect {

    @Pointcut("@annotation(com.example.demo.datasource.annotation.DataSource)")
    public void dataSourcePointCut() {
    }

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource ds = method.getAnnotation(DataSource.class);
        if (ds == null) {
            DynamicDataSourceContextHolder.setDataSourceType(DynamicDataSourceContextHolder.DEFAULT_DATASOURCE_ID);
        } else {
            DynamicDataSourceContextHolder.setDataSourceType(ds.value());
        }
        try {
            return point.proceed();
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }
}

这段代码定义了一个基于注解的数据源切换器,使用 @DataSource 注解标注需要使用的数据源。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value() default "master";
}

1.3 配置动态数据源

@Configuration
public class DataSourceConfig {

    @Bean("masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean("slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        Map<String, DataSource> targetDataSources = new HashMap<>(2);
        targetDataSources.put("master", masterDataSource);
        targetDataSources.put("slave", slaveDataSource);
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        return dynamicDataSource;
    }
}

在这段代码中定义了两个数据源:masterDataSource 和 slaveDataSource,并通过 @Primary 注解指定 masterDataSource 为默认数据源。 DynamicDataSource 是我们自己定义的动态数据源类,实现了动态获取数据源的功能。

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

DynamicDataSourceContextHolder 是一个线程安全的,持有当前线程使用的数据源 ID 的类,典型的 ThreadLocal 实现。

public class DynamicDataSourceContextHolder {

    public static final String DEFAULT_DATASOURCE_ID = "master";

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

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

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

1.4 在代码中使用数据源切换器

在需要使用数据源切换器的方法上,添加 @DataSource 注解,指定需要使用的数据源 ID。

    @GetMapping("/test")
    @DataSource("slave")
    public String test() {
        return jdbcTemplate.queryForObject("select count(*) from user", String.class);
    }

2. 动态添加数据源

2.1 添加数据源配置

首先,在 application.properties 或 application.yml 中定义可扩展的数据源配置项:

druid.datasource.dynamic.datasource.master.driver-class-name=com.mysql.jdbc.Driver
druid.datasource.dynamic.datasource.master.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
druid.datasource.dynamic.datasource.master.username=root
druid.datasource.dynamic.datasource.master.password=123456

这里是采用 Alibaba Druid 数据源,配置了数据源的驱动类,URL,用户名和密码。

2.2 集成 Alibaba Druid

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid-spring-boot-starter</artifactId>
  <version>1.2.6</version>
</dependency>

配置整个应用的数据源为 druid,同时指定开启数据源统计、SQL 监控、数据源定义任务等其他功能。

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        allow: 127.0.0.1
        deny:
      web-stat-filter:
        enabled: true
        url-pattern: /*
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
      filter:
        stat:
          enabled: true
          slowSqlMillis: 3000
          logSlowSql: true
          mergeSql: true
      # 数据源定义任务
      dataSource:
        master:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
          username: root
          password: 123456
        slave:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3307/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
          username: root
          password: 123456

同时在代码中设置 Druid 数据源的初始化参数:

@Bean
public DataSource druidDataSource() {
    DruidDataSource druidDataSource = new DruidDataSource();
    druidDataSource.setUrl(dataSourceConfig.getUrl());
    druidDataSource.setUsername(dataSourceConfig.getUsername());
    druidDataSource.setPassword(dataSourceConfig.getPassword());
    druidDataSource.setDriverClassName(dataSourceConfig.getDriverClassName());

    // 其他自定义参数
    // druidDataSource.setInitialSize();
    // druidDataSource.setMaxActive();
    // druidDataSource.setMinIdle();
    // druidDataSource.setMaxWait();

    return druidDataSource;
}

然后将其配置到 DynamicDataSource 中与其他数据源一起使用。

2.3 手动刷新 Druid 数据源配置信息

当配置文件中的数据源配置项发生改变时, Druid 数据源并不能自动感知这些变化,需要我们手动刷新数据源配置信息。

@Bean
public DataSource druidDataSource() {
    DruidDataSource druidDataSource = new DruidDataSource();
    druidDataSource.setUrl(dataSourceConfig.getUrl());
    druidDataSource.setUsername(dataSourceConfig.getUsername());
    druidDataSource.setPassword(dataSourceConfig.getPassword());
    druidDataSource.setDriverClassName(dataSourceConfig.getDriverClassName());

    // 其他自定义参数
    // druidDataSource.setInitialSize();
    // druidDataSource.setMaxActive();
    // druidDataSource.setMinIdle();
    // druidDataSource.setMaxWait();

    this.initDruidDataSource(druidDataSource, dataSourceConfig);
    return druidDataSource;
}

private void initDruidDataSource(DruidDataSource druidDataSource, DataSourceConfig dataSourceConfig) {
    try {
        Method initMethod = DruidDataSource.class.getDeclaredMethod("init");
        initMethod.setAccessible(true);
        initMethod.invoke(druidDataSource);
        Method configFromPropety = BasicDataSource.class.getDeclaredMethod("configFromPropety", Properties.class);
        configFromPropety.setAccessible(true);
        Properties dataSourceProperties = dataSourceConfig.getDataSourceProperties();
        configFromPropety.invoke(druidDataSource, dataSourceProperties);
        Method setUsername = DruidDataSource.class.getDeclaredMethod("setUsername", String.class);
        setUsername.setAccessible(true);
        setUsername.invoke(druidDataSource, dataSourceConfig.getUsername());
        Method setPassword = DruidDataSource.class.getDeclaredMethod("setPassword", String.class);
        setPassword.setAccessible(true);
        setPassword.invoke(druidDataSource, dataSourceConfig.getPassword());
        Method setUrl = DruidDataSource.class.getDeclaredMethod("setUrl", String.class);
        setUrl.setAccessible(true);
        setUrl.invoke(druidDataSource, dataSourceConfig.getUrl());
        Method setDriverClassName = DruidDataSource.class.getDeclaredMethod("setDriverClassName", String.class);
        setDriverClassName.setAccessible(true);
        setDriverClassName.invoke(druidDataSource, dataSourceConfig.getDriverClassName());
        Method setConnectionProperties = DruidDataSource.class.getDeclaredMethod("setConnectionProperties", String.class);
        setConnectionProperties.setAccessible(true);
        setConnectionProperties.invoke(druidDataSource, dataSourceConfig.getConnectionProperties());
        Method initDbType = DruidDataSource.class.getDeclaredMethod("initDbType");
        initDbType.setAccessible(true);
        initDbType.invoke(druidDataSource);
        Method validateConnection = DruidDataSource.class.getDeclaredMethod("validateConnection");
        validateConnection.setAccessible(true);
        validateConnection.invoke(druidDataSource);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

参考链接

  1. spring动态切换/添加数据源实现
  2. Spring Boot 集成 Druid 实现动态多数据源

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:spring实现动态切换、添加数据源及源码分析 - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • Java8新特性之Lambda表达式浅析

    Java8新特性之Lambda表达式浅析 Lambda表达式是Java8中最重要的新特性之一,它允许将函数作为参数传递,甚至可以创建其它的函数。Lambda表达式的简洁优雅,使得我们能够以更少的代码实现更为复杂的逻辑。本文将深入浅出地讲解Lambda表达式的使用方法及其内部实现细节。 Lambda表达式的基础语法 Lambda表达式使用一组参数和一个函数体组…

    Java 2023年5月26日
    00
  • 详解用Spring Boot零配置快速创建web项目

    使用Spring Boot可以快速创建Web项目,而且不需要进行繁琐的配置。下面是使用Spring Boot零配置创建Web项目的完整攻略: 创建一个Maven项目,并在pom.xml文件中添加以下依赖项: <dependency> <groupId>org.springframework.boot</groupId> &…

    Java 2023年5月14日
    00
  • Java利用Dijkstra算法求解拓扑关系最短路径

    以下是“Java利用Dijkstra算法求解拓扑关系最短路径”的完整攻略。 1. 理解Dijkstra算法 Dijkstra算法是一种单源最短路径算法,用于计算一个节点到图中所有其他节点的最短路径。算法最早由荷兰计算机科学家狄克斯特拉于1959年提出,因此得名。该算法常用于路由算法或作为其他图算法的一个子模块。 Dijkstra算法的基本思想是从起点开始,对…

    Java 2023年5月19日
    00
  • maven配置阿里云仓库的实现方法

    下面是关于”Maven配置阿里云仓库的实现方法”的完整攻略: 为什么需要配置阿里云仓库 Maven是一个可扩展的构建工具,它自带默认仓库地址,但是默认仓库的下载速度非常慢,因此我们可以使用其他仓库镜像来提高下载速度。阿里云提供了Maven的镜像仓库,使用阿里云仓库可大大提高Maven包的下载速度。 阿里云仓库配置方法 在maven/conf/settings…

    Java 2023年5月20日
    00
  • Java非侵入式API接口文档工具apigcc用法详解

    Java非侵入式API接口文档工具apigcc用法详解 概述 apigcc是一款非侵入式的API接口文档生成工具,可以帮助Java开发人员快速生成符合RESTful标准的API接口文档,同时支持多种API文档输出格式,包括HTML、Markdown、PDF等格式。 安装 apigcc可以通过npm安装,使用如下命令即可: npm install apigcc…

    Java 2023年5月20日
    00
  • Spring与Spring boot的区别介绍

    Spring与Spring Boot是Java开发中广泛使用的两个框架,两者之间有明显的区别。本篇攻略将介绍Spring和Spring Boot的区别,以及为何可能会选择使用Spring Boot。 Spring与Spring Boot的区别 Spring框架 Spring框架是一个广泛使用的框架,有以下几点特点: 宽泛的适用范围:Spring框架可以应用于…

    Java 2023年5月15日
    00
  • java8时间 yyyyMMddHHmmss格式转为日期的代码

    下面是详细的攻略。 1. 确定需求 首先,我们需要明确我们的需求是将一个以yyyyMMddHHmmss格式表示的日期时间字符串转换成日期对象。 2. 寻找合适的API 根据Java8的官方文档,我们可以使用java.time.format.DateTimeFormatter类中的parse方法进行字符串解析,将字符串转换为java.time.LocalDat…

    Java 2023年5月20日
    00
  • 一篇文章带你学习JAVA MyBatis底层原理

    一篇文章带你学习JAVA MyBatis底层原理 MyBatis是一个基于Java的ORM框架,它可以将数据库记录映射成对象,屏蔽了大部分的JDBC操作。文章将带你深入了解MyBatis底层原理。我们将分以下四个部分:解析MyBatis类结构、解析MyBatis配置文件、解析Mapper映射文件、MyBatis执行流程。 解析MyBatis类结构 MyBat…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部