Druid多数据源配置(Datasurce动态切换)
提示: 本材料只做个人学习参考,不作为系统的学习流程,请注意识别!!!
一. 直接上代码
- 多数据源切换自定义注解
package com.gateway.admin.datasources.annotation;
import java.lang.annotation.*;
/**
* 多数据源注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String name() default "";
}
- 多数据源,切面处理类
package com.gateway.admin.datasources;
import com.gateway.admin.datasources.annotation.DataSource;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 多数据源,切面处理类
*/
@Aspect
@Component
public class DataSourceAspect implements Ordered {
protected Logger logger = LoggerFactory.getLogger(getClass());
/**
* 针对上面注解做切面拦截
*/
@Pointcut("@annotation(com.gateway.admin.datasources.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){
//如果没有注解,使用默认数据源
DynamicDataSource.setDataSource(DataSourceNames.FIRST);
}else {
//根据注解中设置的数据源名称,选择对应的数据源
DynamicDataSource.setDataSource(ds.name());
logger.debug("set datasource is " + ds.name());
}
try {
return point.proceed();
} finally {
//清除数据源配置
DynamicDataSource.clearDataSource();
}
}
@Override
public int getOrder() {
return 1;
}
}
- 动态数据源类,需要继承:AbstractRoutingDataSource
package com.gateway.admin.datasources;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
/**
* 动态数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
4 .数据源名称配置
package com.gateway.admin.datasources;
/**
* 多数据源配置数据源
*/
public interface DataSourceNames {
String FIRST = "first";
String SECOND = "second";
String THREE = "three";
String FOUR = "four";
}
5 .多数据源配置类
package com.gateway.admin.datasources;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 多数据源配置类
*/
@Configuration
public class DynamicDataSourceConfig {
//如果ioc容器中,同一个类型有多个bean,则bean的名称为方法的名称
@Bean
@ConfigurationProperties("spring.datasource.druid.first")
public DataSource firstDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.second")
public DataSource secondDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.three")
public DataSource threeDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.four")
public DataSource fourDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource, DataSource threeDataSource, DataSource fourDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
targetDataSources.put(DataSourceNames.SECOND, secondDataSource);
targetDataSources.put(DataSourceNames.THREE, threeDataSource);
targetDataSources.put(DataSourceNames.FOUR, fourDataSource);
return new DynamicDataSource(firstDataSource, targetDataSources);
}
}
- yml部分数据源相关配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
druid:
first: #db1
url: jdbc:mysql://127.0.0.1:3306/db1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
second: #db2
url: jdbc:mysql://127.0.0.1:3306/db2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
three: #db3
url: jdbc:mysql://127.0.0.1:3306/db3?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
four: #db4
url: jdbc:mysql://127.0.0.1:3306/db4?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
- 动态调用数据源示例代码
//通过@DataSource以及name,动态指定对应的数据源
@DataSource(name = DataSourceNames.SECOND)
public void test(String param) {
testMapper.test(param);
}
二. AbstractRoutingDataSource 补充
部分源码分析
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
@Nullable//目标数据源
private Map<Object, Object> targetDataSources;
@Nullable//默认数据源
private Object defaultTargetDataSource;
@Nullable//解析的数据源
private Map<Object, DataSource> resolvedDataSources;
@Nullable//解析默认的数据源
private DataSource resolvedDefaultDataSource;
@Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());
this.targetDataSources.forEach((key, value) -> {
Object lookupKey = resolveSpecifiedLookupKey(key);
DataSource dataSource = resolveSpecifiedDataSource(value);
this.resolvedDataSources.put(lookupKey, dataSource);
});
if (this.defaultTargetDataSource != null) {
//设置解析的默认数据源
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
//数据源对应的key,默认和目标数据源map中的key一致
protected Object resolveSpecifiedLookupKey(Object lookupKey) {
return lookupKey;
}
//将指定的数据源对象解析为 DataSource 实例
protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
if (dataSource instanceof DataSource) {
return (DataSource) dataSource;
}
else if (dataSource instanceof String) {
return this.dataSourceLookup.getDataSource((String) dataSource);
}
else {
throw new IllegalArgumentException(
"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
}
}
//==========================================获取数据库连接=========================================
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
//从特定的数据源中获取对应的数据库连接对象
return determineTargetDataSource().getConnection(username, password);
}
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
//DynamicDataSource 中对 determineCurrentLookupKey()方法进行了重写
Object lookupKey = determineCurrentLookupKey();
//resolvedDataSources 对应的map中获取对应的数据源
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
@Nullable
protected abstract Object determineCurrentLookupKey();
}
三. 多数据源 + ShardingJDBC 数据源整合
现有需求: 在多数据源能够动态切换的前提下,其中某个库中的表需要实现分表操作,例如:上述多数据源配置中的second 数据库中某个表需要实现分表操作,我们采用ShardingJDBC实现。此时second 数据源的配置需要更改为ShardingJDBC对应的数据源,如下面更改后的多数据源配置:
原来多数据源配置类
package com.gateway.admin.datasources;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 多数据源配置类
*/
@Configuration
public class DynamicDataSourceConfig {
//如果ioc容器中,同一个类型有多个bean,则bean的名称为方法的名称
@Bean
@ConfigurationProperties("spring.datasource.druid.first")
public DataSource firstDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.second")
public DataSource secondDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.three")
public DataSource threeDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.four")
public DataSource fourDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource, DataSource threeDataSource, DataSource fourDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
targetDataSources.put(DataSourceNames.SECOND, secondDataSource);
targetDataSources.put(DataSourceNames.THREE, threeDataSource);
targetDataSources.put(DataSourceNames.FOUR, fourDataSource);
return new DynamicDataSource(firstDataSource, targetDataSources);
}
}
整合ShardingJDBC 后,多数据源配置类
package com.gateway.admin.datasources;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.shardingsphere.shardingjdbc.jdbc.adapter.AbstractDataSourceAdapter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 多数据源配置类
*/
@Configuration
public class DynamicDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.druid.first")
public DataSource firstDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.three")
public DataSource threeDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.four")
public DataSource fourDataSource() {
return DruidDataSourceBuilder.create().build();
}
//该数据源为shardingjdbc对应的数据源,可通过java代码配置,也可通过配置文件配置,此处我们采用yml配置,配置文件下面会给出
/**
* shardingjdbc有四种数据源,需要根据业务注入不同的数据源
*
* <p>1. 未使用分片, 脱敏的名称(默认): shardingDataSource;
* <p>2. 主从数据源: masterSlaveDataSource;
* <p>3. 脱敏数据源:encryptDataSource;
* <p>4. 影子数据源:shadowDataSource
*
*/
@Lazy
@Resource(name = "shardingDataSource")
private AbstractDataSourceAdapter shardingDataSource;
@Bean
@Primary
public DynamicDataSource dataSource(DataSource firstDataSource, DataSource threeDataSource, DataSource fourDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
targetDataSources.put(DataSourceNames.SECOND, shardingDataSource);
targetDataSources.put(DataSourceNames.THREE, threeDataSource);
targetDataSources.put(DataSourceNames.FOUR, fourDataSource);
return new DynamicDataSource(firstDataSource, targetDataSources);
}
}
对应的yml配置文件为(我们此处只是针对单个库做分表操作):
# Tomcat
server:
tomcat:
uri-encoding: UTF-8
max-threads: 1000
min-spare-threads: 30
port: 8888
spring:
#shardingjdbc--------------------------------------------------------------------------------------------------------
main:
allow-bean-definition-overriding: true
shardingsphere:
props:
sql:
show: true
dataSource:
names: ds0
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/gateway_stable?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
sharding:
tables:
t_order:
logicTable: t_order
actualDataNodes: ds0.t_order$->{1..2}
tableStrategy:
# standard:
# shardingColumn: id
# preciseAlgorithmClassName: com.personal.datasources.MonthPreciseShardingAlgorithm
# rangeAlgorithmClassName:
inline:
shardingColumn: id
algorithmExpression: t_order$->{id % 2 + 1}
keyGenerator:
type: SNOWFLAKE
column: id
worker:
id: 1
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
druid:
first: #数据源1
url: jdbc:mysql://127.0.0.1:3306/wanshu_admin?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
# second: #数据源2
# url: jdbc:mysql://127.0.0.1:3306/gateway_stable?useUnicode=true&characterEncoding=utf-8&useSSL=false
# username: root
# password: root
three: #数据源3
url: jdbc:mysql://127.0.0.1:3306/datacenter_stable?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
four: #数据源4
url: jdbc:mysql://127.0.0.1:3306/chuanglan_risk_control?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
initial-size: 10
max-active: 100
min-idle: 10
max-wait: 60000
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: admin
login-password: admin
filter:
stat:
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: false
wall:
config:
multi-statement-allow: true
mybatis:
mapper-locations: classpath:mapper/**/*.xml
关于shardingJDBC的相关知识,可参考:shardingJDBC分库分表