SpringCloud之nacos共享配置文件实现多数据源灵活切换

时间:2024-07-12 07:21:49

目录

前言

1.引入Springboot相关的aop切面依赖

 2.创建自定义注解@DataSourceKey

3.创建对ThreadLocal类

4.创建aop切面

5.创建动态数据源类

6.创建多数据库连接配置类

7.关键代码讲解

8.nacos主要配置


前言

        通过Spring AOP(面向切面编程)的功能来动态地切换数据源。使用@Aspect和@Component注解,通过切面扫描自定义注解,获取数据源的key,可以在不修改原有业务代码的情况下,在service里面的类方法中加入@DataSourceKey注解,即可访问指定的数据源。

1.引入Springboot相关的aop切面依赖

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

 2.创建自定义注解@DataSourceKey

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSourceKey {
    //默认使用auth数据库
    String value() default "dataSourceSystem";
}

3.创建对ThreadLocal类

        通过线程隔离的方式,实现数据源的切换。

package com.example.auth.datasource;

/**
 * 数据库上下文切换对象,针对每个线程做不同操作
 */
public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSourceKey(String dataSourceKey) {
        contextHolder.set(dataSourceKey);
    }

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

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

4.创建aop切面

        通过包扫描动态切换数据源,主要通过扫描注解的方式获取数据源的key值,即数据源名称。

package com.example.auth.datasource;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.DeclareAnnotation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
 * 多数据源切面
 */
@Aspect
@Component
public class DatasourceAspect {

    private Logger logger = LoggerFactory.getLogger(DatasourceAspect.class);
    @Before("@annotation(dataSourceKey) && execution(* com.example.auth.datasource.*.*(..))")
    public void beforeSwitchDataSource(JoinPoint joinPoint,  DataSourceKey dataSourceKey) {
        String key = dataSourceKey.value();
        logger.info("key:{}",key);
        DataSourceContextHolder.setDataSourceKey(key);
    }

    @Before("@annotation(dataSourceKey) && execution(* com.example.auth.service.*.*(..))")
    public void beforeServiceSwitchDataSource(JoinPoint joinPoint,  DataSourceKey dataSourceKey) {
        String key = dataSourceKey.value();
        logger.info("key:{}",key);
        DataSourceContextHolder.setDataSourceKey(key);
    }

    @After("@annotation(dataSourceKey) && execution(* com.example.auth.service.*.*(..))")
    public void afterServiceSwitchDataSource(JoinPoint joinPoint,  DataSourceKey dataSourceKey) {
        String key = dataSourceKey.value();
        logger.info("key:{}",key);
        DataSourceContextHolder.clearDataSourceKey();
    }

    @After("@annotation(dataSourceKey) && execution(* com.example.auth.datasource.*.*(..)) ")
    public void afterSwitchDataSource(JoinPoint joinPoint, DataSourceKey dataSourceKey) {
        logger.info("key:{}",dataSourceKey.value());
        DataSourceContextHolder.clearDataSourceKey();
    }

}

5.创建动态数据源类

        通过该类,可动态改变数据源名称。

package com.example.auth.datasource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    private Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);
    @Override
    protected Object determineCurrentLookupKey() {
        String key = DataSourceContextHolder.getDataSourceKey();
        logger.info("数据源:{}",key);
        return DataSourceContextHolder.getDataSourceKey();
    }
}

6.创建多数据库连接配置类

package com.example.auth.datasource;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.activation.DataContentHandler;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 多数据源配置
 */
@Configuration
@MapperScan(basePackages = "com.example.auth.mapper")
public class MultiDataSourceConfig {
    private Logger logger  = LoggerFactory.getLogger(MultiDataSourceConfig.class);
    @Autowired
    private DataSource dataSourceAuth;
    @Autowired
    private DataSource dataSourceConsumer;
    @Autowired
    private DataSource dataSourceMq;
    @Autowired
    private DataSource dataSourceGateway;
    @Autowired
    private DataSource dataSourceSystem;
    @Autowired
    @Qualifier("dynamicDataSource")
    private DataSource dynamicDataSource;
    @Autowired
    private StringEncryptor stringEncryptor;
    @Bean(name = "dataSourceAuth")
    @ConfigurationProperties(prefix = "spring.datasource.auth")
    public DataSource dataSourceAuth() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "dataSourceConsumer")
    @ConfigurationProperties(prefix = "spring.datasource.consumer")
    public DataSource dataSourceConsumer() {

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

        return DataSourceBuilder.create().build();
    }

    @Bean(name = "dataSourceGateway")
    @ConfigurationProperties(prefix = "spring.datasource.gateway")
    public DataSource dataSourceGateway() {

        return DataSourceBuilder.create().build();
    }
    @Bean(name = "dataSourceSystem")
    @ConfigurationProperties(prefix = "spring.datasource.system")
    public DataSource dataSourceSystem() {
        return DataSourceBuilder.create().build();
    }
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        //注册乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }

    @Bean
    public StringEncryptor stringEncryptor() {
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        config.setPassword("encryptionkey"); // 加密密钥
        config.setAlgorithm("PBEWithHmacSHA512AndAES_256");
        config.setKeyObtentionIterations("1000");
        config.setPoolSize("1");
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        config.setStringOutputType("base64");
        encryptor.setConfig(config);
        return encryptor;
    }
    @PostConstruct
    public void init(){
      /* String  enStr  = stringEncryptor.encrypt("Root@123");
       String  deSTr  = stringEncryptor.decrypt("N8VBWG5nOHvy5efX3/mlPAmdBykE7iDZFl362LyeaPRXMbLT0PzEIlB/KDXrNYz6");
        System.out.println("enStr==="+enStr);
        System.out.println("deSTr==="+deSTr);*/
    }
    /**
     * 不加
     * @param interceptor
     * @return
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory (MybatisPlusInterceptor interceptor) throws Exception {
        MybatisSqlSessionFactoryBean ssfb = new MybatisSqlSessionFactoryBean();
        ssfb.setDataSource(dynamicDataSource); // 使用 DynamicDataSource
        ssfb.setPlugins(interceptor);
        ssfb.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:/mapper/*Mapper.xml"));
        return ssfb.getObject();
    }

    @Bean
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 假设你有多个数据源,需要在这里将它们添加到 targetDataSources 中
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("dataSourceSystem", dataSourceSystem);
        targetDataSources.put("dataSourceAuth", dataSourceAuth);
        targetDataSources.put("dataSourceConsumer", dataSourceConsumer);
        targetDataSources.put("dataSourceMq", dataSourceMq);
        targetDataSources.put("dataSourceGateway",dataSourceGateway);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(dataSourceSystem);// 设置默认数据源
        return dynamicDataSource;
    }

}

7.关键代码讲解

        注入dynamicDataSource实体,通过该实体bean动态获取数据源,从而达到随意切换数据源的目的。

        单个dataSource的注入,如 dataSourceAuth,主要是给动态数据源的切换提前准备多数据源。

8.nacos主要配置

spring:
    datasource:
        system: 
            driver-class-name: com.mysql.cj.jdbc.Driver
            jdbc-url: jdbc:mysql://localhost:3306/system?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&nullCatalogMeansCurrent=true
            username: root
            password: ENC(N8VBWG5nOHvy5efX3/mlPAmdBykE7iDZFl362LyeaPRXMbLT0PzEIlB/KDXrNYz6)
            type: com.alibaba.druid.pool.DruidDataSource
            druid:
                initial-size: 5
                min-idle: 1
                max-active: 10
                max-wait: 60000
                validation-query: SELECT 1 FROM DUAL
                test-on-borrow: false
                test-on-return: false
                test-while-idle: true
                time-between-eviction-runs-millis: 60000
        auth: 
            driver-class-name: com.mysql.cj.jdbc.Driver
            jdbc-url: jdbc:mysql://localhost:3306/auth?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&nullCatalogMeansCurrent=true
            username: root
            password: ENC(N8VBWG5nOHvy5efX3/mlPAmdBykE7iDZFl362LyeaPRXMbLT0PzEIlB/KDXrNYz6)
            type: com.alibaba.druid.pool.DruidDataSource
            druid:
                initial-size: 5
                min-idle: 1
                max-active: 10
                max-wait: 60000
                validation-query: SELECT 1 FROM DUAL
                test-on-borrow: false
                test-on-return: false
                test-while-idle: true
                time-between-eviction-runs-millis: 60000
        consumer: 
            driver-class-name: com.mysql.cj.jdbc.Driver
            jdbc-url: jdbc:mysql://localhost:3306/consumer?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&nullCatalogMeansCurrent=true
            username: root
            password: ENC(N8VBWG5nOHvy5efX3/mlPAmdBykE7iDZFl362LyeaPRXMbLT0PzEIlB/KDXrNYz6)
            type: com.alibaba.druid.pool.DruidDataSource
            druid:
                initial-size: 5
                min-idle: 1
                max-active: 10
                max-wait: 60000
                validation-query: SELECT 1 FROM DUAL
                test-on-borrow: false
                test-on-return: false
                test-while-idle: true
                time-between-eviction-runs-millis: 60000
        mq: 
            driver-class-name: com.mysql.cj.jdbc.Driver
            jdbc-url: jdbc:mysql://localhost:3306/mq?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&nullCatalogMeansCurrent=true
            username: root
            password: ENC(N8VBWG5nOHvy5efX3/mlPAmdBykE7iDZFl362LyeaPRXMbLT0PzEIlB/KDXrNYz6)
            type: com.alibaba.druid.pool.DruidDataSource
            druid:
                initial-size: 5
                min-idle: 1
                max-active: 10
                max-wait: 60000
                validation-query: SELECT 1 FROM DUAL
                test-on-borrow: false
                test-on-return: false
                test-while-idle: true
                time-between-eviction-runs-millis: 60000
        gateway: 
            driver-class-name: com.mysql.cj.jdbc.Driver
            jdbc-url: jdbc:mysql://localhost:3306/gateway?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&nullCatalogMeansCurrent=true
            username: root
            password: ENC(N8VBWG5nOHvy5efX3/mlPAmdBykE7iDZFl362LyeaPRXMbLT0PzEIlB/KDXrNYz6)
            type: com.alibaba.druid.pool.DruidDataSource
            druid:
                initial-size: 5
                min-idle: 1
                max-active: 10
                max-wait: 60000
                validation-query: SELECT 1 FROM DUAL
                test-on-borrow: false
                test-on-return: false
                test-while-idle: true
                time-between-eviction-runs-millis: 60000