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日

相关文章

  • apache commons工具集代码详解

    Apache Commons工具集代码详解 什么是Apache Commons Apache Commons是Apache软件基金会提供的一套开源工具集,用于Java开发。它提供了许多实用的Java类和组件,可以帮助开发者快速开发各种应用程序,提高开发效率。 Apache Commons的组件 Apache Commons工具集包含了很多组件,每个组件都提供…

    Java 2023年5月19日
    00
  • java中几种常见的排序算法总结

    对于“java中几种常见的排序算法总结”的攻略,我们可以通过以下步骤来进行详细讲解: 一、排序算法简介 在介绍具体的排序算法之前,我们需要了解一些基础概念。排序算法是指对一个数据集合进行排序的过程,其中涉及到的一些重要概念包括: 稳定性:如果存在相同的元素,排序前和排序后这些元素的相对位置是否发生了改变。稳定的排序算法会保留相同元素之间的顺序关系,不稳定的排…

    Java 2023年5月19日
    00
  • 微信小程序获取用户手机号码的详细步骤

    获取微信小程序用户手机号码的详细步骤包括以下三步骤: 用户授权获取手机号码 获取用户信息中的手机号码 解密用户敏感数据以获取手机号码 下面将详细介绍这三步骤。 步骤一:用户授权获取手机号码 用户需要授权小程序获取其手机号码。在小程序中,可以使用<button>或<open-type>来触发获取手机号码的授权。 例如,以下是一个获取用户…

    Java 2023年5月19日
    00
  • 什么是Java字节码操纵库?

    Java字节码操纵库是一种操作Java字节码的工具,它允许开发者在不影响源代码的情况下,对Java字节码进行修改、生成和分析等操作。下面是Java字节码操纵库的完整使用攻略。 引入Java字节码操纵库 Java字节码操纵库包括了多个开源项目,比较常用的有ASM、Javassist、Byte Buddy等。以ASM为例,可以在Maven或Gradle的配置文件…

    Java 2023年5月11日
    00
  • java使用URLDecoder和URLEncoder对中文字符进行编码和解码

    下面是“java使用URLDecoder和URLEncoder对中文字符进行编码和解码”的完整攻略。 什么是URL编码和解码? 在URL中,一些字符可能具有特殊含义。例如,空格字符被视为“+”号,或者被编码为“%20”。URL编码就是将不安全的字符转换为%后跟两个十六进制数的形式。而URL解码则是将这些转义字符还原为它们本来的字符形式。 java中使用URL…

    Java 2023年5月20日
    00
  • hadoop入门之通过java代码实现将本地文件上传到hadoop的文件系统

    下面是 “Hadoop入门之通过Java代码实现将本地文件上传到Hadoop的文件系统”的攻略。 步骤一:安装Hadoop 首先需要安装配置好Hadoop。具体安装过程这里不再赘述,可以参考官方文档:https://hadoop.apache.org/docs/r3.2.2/index.html 步骤二:引入Hadoop的依赖包 在java项目中使用Hado…

    Java 2023年5月20日
    00
  • Java JVM编译策略案例详解

    当我们编写Java程序时,代码是无法直接被计算机识别的,需要通过一种特殊的编译器将其转换成可被计算机执行的字节码,而Java虚拟机(JVM)则负责将字节码解释为对应的机器指令并执行。在这个过程中,JVM的编译器对字节码的编译策略扮演着重要的角色,选择合适的编译策略有助于提高程序执行效率。下面将详细讲解Java JVM编译策略的攻略,包括编译模式、编译等级、缓…

    Java 2023年5月19日
    00
  • JavaEE微框架Spring Boot深入解读

    JavaEE微框架SpringBoot深入解读 简介 Spring Boot是一个基于Spring框架的快速应用开发框架,它简化了Spring应用的开发过程,使用起来非常方便,而且能够快速地搭建一个可用的、生产级别的应用程序。 Spring Boot的核心特性 自动配置 在Spring Boot的自动配置下,开发者不需要再手动地为每一个框架、类库引入一个配置…

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