下面是关于“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);
}
}
参考链接
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:spring实现动态切换、添加数据源及源码分析 - Python技术站