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

yizhihongxing

下面是关于“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日

相关文章

  • Spring Security OAuth2实现使用JWT的示例代码

    下面就为大家详细讲解一下Spring Security OAuth2实现使用JWT的示例代码的完整攻略,过程中会包含两条示例。 背景介绍 在微服务和云计算的时代,OAuth2成为了认证和授权的标准协议。Spring Security是一个基于Spring的安全框架,允许您在应用中实现安全控制。而JWT(JSON Web Token)是一种基于JSON的标准,…

    Java 2023年5月20日
    00
  • Redis监听过期的key实现流程详解

    标题:Redis监听过期的key实现流程详解 什么是Redis过期key机制 Redis是一种内存数据库,对于内存这个资源,我们肯定是要最大化利用的。Redis对于过期key的机制,能够自动判断某个key是否过期,对于过期key进行删除,及时释放内存资源。 Redis过期机制的实现方式 Redis内部实现了一个定时任务,每隔一段时间就会查找是否有过期的key…

    Java 2023年5月20日
    00
  • Java 模拟数据库连接池的实现代码

    这里为大家介绍一下 Java 模拟数据库连接池的实现代码的完整攻略。 准备工作 在开始实现之前,我们需要引入一些必要的类库和工具,这些工具包括: java.sql 包中的 JDBC API,用于连接数据库。 com.zaxxer.hikari.HikariConfig, com.zaxxer.hikari.HikariDataSource, com.zaxx…

    Java 2023年5月19日
    00
  • dockerfile-maven-plugin极简教程(推荐)

    下面是“dockerfile-maven-plugin极简教程(推荐)”的完整攻略: 1. 简介 dockerfile-maven-plugin是一个maven插件,可以将maven项目构建成Docker镜像。通过dockerfile-maven-plugin,我们可以将应用程序打包成Docker镜像并快速部署。 2. 安装 在pom.xml文件中添加以下依…

    Java 2023年5月20日
    00
  • 200行Java代码编写一个计算器程序

    这是一个关于编写计算器程序的攻略,本文旨在帮助读者快速掌握200行Java代码编写一个计算器程序的完整过程。 环境准备 首先,我们需要准备好Java开发环境。如果你还没有安装Java环境,请先下载并安装Java JDK。 接下来,我们将使用IntelliJ IDEA作为开发工具。如果你还没有安装IntelliJ IDEA,请先下载并安装该工具。 创建项目 打…

    Java 2023年5月23日
    00
  • Java单例的写法详解

    Java中的单例模式,指的是确保一个类只有一个实例,并提供访问该实例的全局访问点。这在某些情况下非常有用,例如当有一个全局资源,如线程池、数据库连接池等,需要在应用程序的整个生命周期内保持一致时。下面是Java单例模式的写法详解。 懒汉式单例模式 实现方式 懒汉式单例模式是指在需要使用实例的时候才去创建,而不是在类加载时就创建。懒汉式单例模式可以通过两种方式…

    Java 2023年5月23日
    00
  • 关于RequestMapping注解的作用说明

    关于@RequestMapping注解的作用说明 @RequestMapping注解是Spring框架中最常用的注解之一,它可以用来映射URL和处理HTTP请求,是控制器中的一个方法级别的注解。下面将详细介绍@RequestMapping的作用和使用说明。 基本作用 @RequestMapping注解用于将指定的URL映射到处理请求的控制器方法上。当请求UR…

    Java 2023年6月15日
    00
  • 关于iframe的一点发现与思考

    那么首先让我们来解释一下文章标题中提到的 iframe 是什么东西。 什么是 iframe? iframe 是一种 HTML 元素,用于在当前页面中嵌入其他网页。通过 iframe,我们可以在一张网页中嵌入另一个网页,并且可以在我们网页的其他元素之上或之下显示它。 例如,下面这段 HTML 代码通过 iframe 将百度搜索界面嵌入到当前页面中: <i…

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