目录
前言
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