一. 示例代码 (druid-spring-boot-starter建议使用1.1.10及以上版本)
- 多数据源切换自定义注解
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 // Primary 一定要写在这里,写上面任意一个bean相当于指定唯一数据源 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); } }
6.多数据源配置类
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&serverTimezone=UTC
username: root
password: root
second: #db2
url: jdbc:mysql://127.0.0.1:3306/db2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
username: root
password: root
three: #db3
url: jdbc:mysql://127.0.0.1:3306/db3?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
username: root
password: root
four: #db4
url: jdbc:mysql://127.0.0.1:3306/db4?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
username: root
password: root
7.单侧
@Service
public class SysConfigServerce {
@Autowired
SysConfigMapper sysConfigMapper;
@DataSource(name = DataSourceNames.FIRST)
public SysConfig select(SysConfig sysConfig){
return sysConfigMapper.selectConfig(sysConfig);
}
}
public class DynamicDataSourceTest extends BaseTest{
@Autowired
private SysConfigServerce sysConfigServerce;
@Test
public void test(){
SysConfig sysConfig = new SysConfig();
sysConfig.setConfigId(1L);
SysConfig sysConfig1 = sysConfigServerce.select(sysConfig);
System.out.println(sysConfig1);
}
}
二. 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://172.16.43.43: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://172.16.43.43:3306/datacenter_stable?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
four: #数据源4
url: jdbc:mysql://172.16.43.43: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
相关阅读:
dynamic-datasource-spring-boot-starter
版权声明:本文为CSDN博主「过了火的你」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43695916/article/details/119210008