0%

springBoot配置多数据源以及动态切换

在项目中需要用到多数据源的切换,主要用到AbstractRoutingDataSource类来实现这个功能。

AbstractRoutingDataSource是Spring-jdbc中一个调用路由到各种目标 DataSource 之一的抽象类。

通过determineCurrentLookupKey()方法来指定路由,切换数据源通过AOP加自定义注解实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

//存放多数据源
@Nullable
private Map<Object, Object> targetDataSources;

//没有指定数据源时的默认数据源
@Nullable
private Object defaultTargetDataSource;

//存放多数据源 通过InitializingBean接口来将targetDataSources赋值给resolvedDataSources
@Nullable
private Map<Object, DataSource> resolvedDataSources;

@Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = CollectionUtils.newHashMap(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);
}
}

代码如下:

DynamicDataSource

1
2
3
4
5
6
7
8
9
//指定多数据源的路由方式
public class DynamicDataSource extends AbstractRoutingDataSource {

//覆盖该方法,DbContextHolder存放当前线程的数据源名称键值
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
}

DbContextHolder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//存放当前线程数据源名称  即map中的key
public class DbContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

/**
* 设置数据源
*
* @param dbType
*/
public static void setDbType(String dbType) {
CONTEXT_HOLDER.set(dbType);
}

/**
* 取得当前数据源
*
* @return
*/
public static String getDbType() {
String type = CONTEXT_HOLDER.get();
if (StringUtils.isEmpty(type)) {
return null;
}
return type;
}

/**
* 清除上下文数据
*/
public static void clearDbType() {
CONTEXT_HOLDER.remove();
}

DatasourceConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//配置多Datasource
@Configuration
public class DatasourceConfig implements EnvironmentAware {

private static String BASE_PATH = "spring.datasource.druid.";

private Environment environment;

@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}


@Bean("dynamicDatasource")
public DynamicDataSource dynamicDataSource() {
String dbNames = environment.getProperty("spring.datasource.names");
String[] split = dbNames.split(",");
Map<Object, Object> targetDataSources = new LinkedHashMap<>(2 << 2);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
for (String str : split) {
DruidDataSource druidDataSource = initDruidDataSource(str);
targetDataSources.put(str, druidDataSource);
}
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(targetDataSources.entrySet().iterator().next().getValue());
return dynamicDataSource;
}

public DruidDataSource initDruidDataSource(String dbName) {
String driverClassName = environment.getProperty(BASE_PATH + dbName + ".driver-class-name");
String url = environment.getProperty(BASE_PATH + dbName + ".url");
String username = environment.getProperty(BASE_PATH + dbName + ".username");
String password = environment.getProperty(BASE_PATH + dbName + ".password");
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
}

DS

1
2
3
4
5
6
7
8
//自定义注解,指定数据源
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface DS {
String value();
}

DSAspect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//通过AOP来为DbContextHolder指定数据源名称来动态切换数据源
@Aspect
@Component
public class DSAspect {


@Around(value = "@annotation(ds)")
public Object around(final ProceedingJoinPoint joinPoint, DS ds) throws Throwable {
try {
String value = ds.value();
DbContextHolder.setDbType(value);
Object proceed = joinPoint.proceed();
return proceed;
}catch (Throwable throwable) {
throw throwable;
}finally {
DbContextHolder.clearDbType();
}
}
}