项目中经常会遇到一些多数据源的问题,如何在项目中切换呢?
baomidou就是个不错的切换数据源的组件。
pom文件配置
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
项目配置(使用duri连接池)
spring:
dataSource:
dynamic:
primary: mysql
p6spy: true
lazy: true
druid:
web-stat-filter:
enabled: false
stat-view-servlet:
enabled: false
max-active: 100
min-idle: 10
max-wait: 5000
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
time-between-eviction-runs-millis: 600000
min-evictable-idle-time-millis: 300000
test-while-idle: true
test-on-borrow: false
test-on-return: false
filter:
stat:
log-slow-sql: true
slow-sql-millis: 3000
merge-sql: true
wall:
config:
multi-statement-allow: true
initial-size: 10
fail-fast: true
kill-when-socket-read-timeout: true
phy-timeout-millis: 3000
break-after-acquire-failure: false
not-full-timeout-retry-count: -1
connection-error-retry-attempts: 0
remove-abandoned: true
remove-abandoned-timeout: 180
datasource:
mysql:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1“3306/test?useSSL=false&requireSSL=false&characterEncoding=UTF-8&rewriteBatchedStatements=true
username: root
password: 12345678
oracle:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: oracle.jdbc.driver.OracleDriver
url: jdbc:oracle:thin:@127.0.0.1:1521:orcl
username: system
password: 12345678
代码中使用
@DS("oracle")
public List<AShareEODPrices> getTodayTrade(){
List<AShareEODPrices> list= pricesMapper.getAll();
return list;
}
注: @DS注解可以加在方法、类上。
@DS注解其实通过AOP做的代理,来实现切换数据库源的。
所以,事务、定时任务这些场景使用@DS注解其实并不生效。
目前来说切换数据源失效主要有以下几个场景:(网上查到的)
那么问题来了。。。 现在我需要在定时任务以及内部线程使用@DS切换数据源,怎么办???
先分析一下baomidou的源码。
主要看两个类 :
/**
* Core Interceptor of Dynamic Datasource
*
* @author TaoYu
* @since 1.2.0
*/
public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {
/**
* The identification of SPEL.
*/
private static final String DYNAMIC_PREFIX = "#";
private final DataSourceClassResolver dataSourceClassResolver;
private final DsProcessor dsProcessor;
public DynamicDataSourceAnnotationInterceptor(Boolean allowedPublicOnly, DsProcessor dsProcessor) {
dataSourceClassResolver = new DataSourceClassResolver(allowedPublicOnly);
this.dsProcessor = dsProcessor;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String dsKey = determineDatasourceKey(invocation);
DynamicDataSourceContextHolder.push(dsKey);//看这里 看这里。 他push了dskey。这个就是注解@DS设置的数据库名em。。
try {
return invocation.proceed();
} finally {
DynamicDataSourceContextHolder.poll();//看这里 他释放了,所以下面我们要看DynamicDataSourceContextHolder这个是啥了
}
}
private String determineDatasourceKey(MethodInvocation invocation) {
String key = dataSourceClassResolver.findDSKey(invocation.getMethod(), invocation.getThis());
return (!key.isEmpty() && key.startsWith(DYNAMIC_PREFIX)) ? dsProcessor.determineDatasource(invocation, key) : key;
}
}
/**
* 核心基于ThreadLocal的切换数据源工具类
*
* @author TaoYu Kanyuxia
* @since 1.0.0
*/
public final class DynamicDataSourceContextHolder {
/**
* 为什么要用链表存储(准确的是栈)
* <pre>
* 为了支持嵌套切换,如ABC三个service都是不同的数据源
* 其中A的某个业务要调B的方法,B的方法需要调用C的方法。一级一级调用切换,形成了链。
* 传统的只设置当前线程的方式不能满足此业务需求,必须使用栈,后进先出。
* </pre>
*/
private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new NamedThreadLocal<Deque<String>>("dynamic-datasource") {
@Override
protected Deque<String> initialValue() {
return new ArrayDeque<>();
}
};
private DynamicDataSourceContextHolder() {
}
/**
* 获得当前线程数据源
*
* @return 数据源名称
*/
public static String peek() {
return LOOKUP_KEY_HOLDER.get().peek();
}
/**
* 设置当前线程数据源
* <p>
* 如非必要不要手动调用,调用后确保最终清除
* </p>
*
* @param ds 数据源名称
*/
public static String push(String ds) {
String dataSourceStr = StringUtils.isEmpty(ds) ? "" : ds;
LOOKUP_KEY_HOLDER.get().push(dataSourceStr);
return dataSourceStr;
}
/**
* 清空当前线程数据源
* <p>
* 如果当前线程是连续切换数据源 只会移除掉当前线程的数据源名称
* </p>
*/
public static void poll() {
Deque<String> deque = LOOKUP_KEY_HOLDER.get();
deque.poll();
if (deque.isEmpty()) {
LOOKUP_KEY_HOLDER.remove();
}
}
/**
* 强制清空本地线程
* <p>
* 防止内存泄漏,如手动调用了push可调用此方法确保清除
* </p>
*/
public static void clear() {
LOOKUP_KEY_HOLDER.remove();
}
}
看看源码上的注释说的很清楚了,其内部就是通过threadlocal来做的数据源切换。那么@DS注解其实就是为了给当前进程设置threadlocal,所以为什么这一步我们不能自己做呢???
所以手动切换数据库
public class UploadStockPrisesThread extends Thread{
@Override
public void run() {
//手动设置pracle数据库
DynamicDataSourceContextHolder.push("oracle");
try {
//doJob
}catch (Exception e) {
e.printStackTrace();
}finally {
//自己释放
DynamicDataSourceContextHolder.poll();
}
}
}
这样就可以在异步线程、定时任务中随意切换数据源了
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved