当前位置: 首页 > article >正文

springboot + druid-spring-boot-starter + mysql 实现动态多数据源

1. 需要导入的 maven 依赖

      <dependency>

          <groupId>com.alibaba</groupId>

          <artifactId>druid-spring-boot-starter</artifactId>

          <version>1.1.10</version>

      </dependency>

2. 配置信息(*.yml)

# 数据源配置
spring:
  datasource:
    druid:
      master: # 主库数据源
        url: jdbc:mysql://localhost:3306/qi_e
        username: root
        password: 123456   #密码,可以配置密文密码
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        filters: config,stat,wall,slf4j #stat: sql 信息监控统计,wall:防注入,slf4j:日志打印
        #connect-properties:  #自定义密码解密信息,需要配合自定义密码解密类
        #  key: test_public_keys  # 密码解密key 
        #  pwd: BljexasdfwdafeasdfaA==  # 加密密码
        #password-callback-class-name: com.xx.ss.xx.CustomPasswordDecrypt#自定义解密类路径
        initialSize: 5 # 初始连接数
        minIdle: 10    # 最小连接池数量
        maxActive: 20  # 最大连接池数量
        mxWait: 60000 # 配置获取连接等待超时的时间
        connectTimeout: 30000 # 配置连接超时时间
        socketTimeout: 60000  # 配置网络超时时间
        timeBetweenEvictionRunsMillis: 60000 d# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒
        maxEvictableIdleTimeMillis: 900000 # 配置一个连接在池中最大生存的时间,单位是毫秒
      slave: # 从库数据源
        url: jdbc:mysql://localhost:3306/qi_e2
        username: root
        password: 123456
        type: com.alibaba.druid.pool.DruidDataSource
        filters: config,stat,wall,slf4j #stat: sql 信息监控统计,wall:防注入,slf4j:日志打印
        # 密码加密方式1
        #connect-properties:  #自定义密码解密信息,需要配合自定义密码解密类
        #  key: test_public_keys  # 密码解密key 
        #  pwd: BljexasdfwdafeasdfaA==  # 加密密码
        #password-callback-class-name: com.xx.ss.xx.CustomPasswordDecrypt#自定义解密类路径
        driverClassName: com.mysql.cj.jdbc.Driver
        initialSize: 5 # 初始连接数
        minIdle: 10    # 最小连接池数量
        maxActive: 20  # 最大连接池数量
        mxWait: 60000 # 配置获取连接等待超时的时间
        connectTimeout: 30000 # 配置连接超时时间
        socketTimeout: 60000  # 配置网络超时时间
        timeBetweenEvictionRunsMillis: 60000 d# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒
        maxEvictableIdleTimeMillis: 900000 # 配置一个连接在池中最大生存的时间,单位是毫秒 
      filter: # 多数据源统一配置信息
        stat:
          log-slow-sql: true # 开启慢 sql 日志
          slow-sql-millis: 500 # 设置慢 sql 阈值
          enabled: true # 开启监测
        slf4j:
          statement-log-error-enabled: true
          statement-exectable-sql-log-enable: true
          enabled: true
      stat-view-servlet: #配置监控信息展示页面,可以能过访问监控地址判断配置是否生效
        enabled: true    # 页面访问地址 localhost:8080(项目端口)/druid
        url-pattern: /druid/*
        login-username: root
        login-password: root

 3. 接入多数据源信息

    3.1 配置类加载配置信息
package com.test.datasources;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
/**
 * 配置多数据源
 */
@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    public DataSource slaveDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary //这里要注意,多数据源必须给当前 bean 标记为主数据源类,@Lazy 最好加上防止循环依赖
    public DynamicDataSource dataSource(@Lazy DataSource masterDataSource, @Lazy DataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource);
        targetDataSources.put("salve", slaveDataSource);
        return new DynamicDataSource(firstDataSource, targetDataSources);
    }

    @Bean
    public JdbcTemplate jdbcTemplate() {
     return new JdbcTemplate(masterDataSource());
    }
}
   3.2 实现多数据源处理类,接入多数据源信息
package com.test.datasources;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
 * 动态数据源,关键代码,借助本地线程动态切换数据源
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

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

    public DynamicDataSource(DataSource defaultDataSource, Map<Object, Object> targetDataSourceMap) {
        // 默认数据源信息
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        // 数据源集合
        super.setTargetDataSources(targetDataSourceMap);
        // 注意:初始化数据源信息方法,一定要调用
        super.afterPropertiesSet();
    }

    @Override //注意:该方法为数据源切换方法,在访问数据库前会调用该方法获取当前要连接的数据信息
    protected Object determineCurrentLookupKey() {
        return getDataSource();
    }

    // 设置当前线程要访问的数据源 key, 该key用于获取 targetDataSourceMap 中对应的数据源
    public static void setDataSource(String dataSource) {
        dataSSourceThreadLocal.set(dataSource);
    }

    public static String getDataSource() {
        return dataSSourceThreadLocal.get();
    }

    public static void clearDataSource() {
        dataSSourceThreadLocal.remove();
    }

}

4.手动切换数据源

package com.test.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.test.datasources.DataSourceNames;
import com.test.datasources.annotation.DataSource;
import com.test.entity.Test1Entity;
import com.test.mapper.Test1Dao;

@Service
public class Test1Service {

	@Autowired
	private Test1Dao test1Dao;

	public Test1Entity getById(int id) {
        // 调用数据库之前,先设置当前线程要切换的数据源信息,
        // 当前使用的是 master 数据源,也可以改为 slave 数据源
        DynamicDataSource.setDataSource("master");
        // 调用数据库
        Test1Entity test1Entity = test1Dao.getById(id);
        // 清除当前线程数据源配置信息,最好放在 finilly 中
        DynamicDataSource.clearDataSource();
		return test1Entity;
	}
}

5.使用AOP + 注解 实现动态切换数据源

    5.1 自定义注解
package com.test.datasources.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 多数据源注解,可以在方法和类添加该注解
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS{

    // 数据源类型
    String dsType() default "";

}
  5.2 实现 AOP  
package com.test.datasources.aspect;
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import com.test.datasources.DataSourceNames;
import com.test.datasources.DynamicDataSource;
import com.test.datasources.annotation.DataSource;
/**
 * 多数据源,切面处理类
 */
@Aspect
@Component
public class DSAspect {
    /**
     * 切点匹配指定注解的方法 
     */
    @Pointcut("@annotation(com.test.datasources.annotation.DS))
    public void dataSourcePointCut() {
    }

    /**
     * 切面处理多数据源注解指定方法
     */
    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method dsMethod = signature.getMethod().getAnnotation(DS.class);
        // 判断是否获取到注解信息,如果获取到信息使用注解中配置的信息
        if(dsMethod != null){
        	DynamicDataSource.setDataSource(dsMethod.value());
        }else{
        	//如果都不存在,则默认使用 master 数据源
        	DynamicDataSource.setDataSource("master");
        }
        try {
            return point.proceed();
        } finally {
            // 清除当前线程数据源信息
            DynamicDataSource.clearDataSource();
        }
    }
}
  5.3 使用注解动态切换数据源
package com.test.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.test.datasources.DataSourceNames;
import com.test.datasources.annotation.DataSource;
import com.test.entity.Test1Entity;
import com.test.mapper.Test1Dao;

@Service
public class Test1Service {

	@Autowired
	private Test1Dao test1Dao;

    @DS("slave") // 使用 slave 数据源获取信息
	public Test1Entity getById(int id) {
		return test1Dao.getById(id);
	}
}

6. druid-spring-boot-stater 实现多数据源分析

    mybatis 执行查询 SQL 会经过一下几个流程,先获取 SqlSession 对象, 由 SqlSession 对象调用executor 对象执行 query 方法,该方法在做预解析时会去获取链接信息,走到我们编写的类(DynamicDataSource extends AbstractRoutingDataSource) 并调用 determineCurrentLookupKey 方法获取目标数据源,在我们实现类中会获取当前线程本地缓存的 key (master或slave),然后根据 key 获取最终的数据库连接信息,执行 sql 获取数据 

7.多数据源链接密码加密

   7.1 配置文件添加配置信息 

        connect-properties:  #自定义密码解密信息,需要配合自定义密码解密类
          key: test_public_keys  # 密码解密key 
          pwd: BljexasdfwdafeasdfaA==  # 加密密码
        password-callback-class-name: com.xx.ss.xx.CustomPasswordDecrypt#自定义解密类路径

    7.2 自定义加密解密类
public class CustomPasswordDecrypt extends DruidPasswordCallback{

    @Override
    public void setProperties(Properties properties) {
        // 公共key
        String key = properties.get("key");
        // 加密密码
        String encryptPwd = properties.get("pwd");
        // 解密密码
        String pwd = decrpt(key,encryptPwd);
        // 设置密码         
        supper.setPassword(pwd.toCharArray());
    }

  // 加密
  public String encrpt(String key, String pwd){
       SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8),"AES");
       Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
       cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec);
       byte[] bytes = cipher.doFinal(pwd.getBytes(StandardCharsets.UTF_8));
       return Base64.getEncoder().encodeToString(bytes);
   }

   // 解密
   public String decrpt(String key, String encryptPwd){
       byte[] bytes = new byte[0];
       try{
         byte[] decode = Base64.getDecoder().decode(encryptPwd);
         SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8),"AES");
         Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
         cipher.init(Cipher.DECRYPT_MODE,secretKeySpec);
         bytes = cipher.doFinal(decode);
       } catch (Exception e) {
         LOGGER.info("解密失败!{}",e);
       }
       return new String(bytes,StandardCharsets.UTF_8);
   }

}

http://www.kler.cn/news/337062.html

相关文章:

  • Linux实践|设置静态 IP 地址
  • 物理学基础精解【54】
  • MySql索引(index)
  • 第二十二天|回溯算法| 理论基础,77. 组合(剪枝),216. 组合总和III,17. 电话号码的字母组合
  • 工业缺陷检测深度学习方法
  • Python深度学习进阶与前沿应用:注意力机制、Transformer模型、生成式模型、目标检测算法、图神经网络、强化学习等
  • platformio.ini工程配置文件入门
  • Linux NFS 服务器 搭建
  • C++仿函数( 调用运算符重载)
  • 【JS】在 Node.js 和 Electron 中获取设备 UUID 的最佳实践
  • Leetcode203.移除链表元素-Python
  • 输电线路悬垂线夹检测无人机航拍图像数据集,总共1600左右图片,悬垂线夹识别,标注为voc格式
  • 【RabbitMQ】RabbitMQ学习
  • 钉钉x昇腾:用AI一体机撬动企业数字资产智能化
  • sqli-labs靶场第二关less-2
  • 在macOS上进行开发环境配置与应用开发详细的配置指南
  • k8s 之安装busybox
  • 【Python】Python知识总结浅析
  • 【SQL】掌握SQL查询技巧:数据筛选与限制
  • whereis命令:查找命令的路径