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);
}
}
原文地址:https://blog.csdn.net/sdfsfaffs/article/details/142740016
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!