前言
这是我在这个网站整理的笔记,关注我,接下来还会持续更新。 作者:神的孩子都在歌唱
项目初始化的时候会调用com.baomidou.dynamic.datasource.DynamicRoutingDataSource对象的addDataSource方法添加数据源,数据源存进dataSourceMap中。
进行数据操作时,方法会被com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationInterceptor
拦截
在 intercept
方法中,会解析方法上的 @DS
注解,获取注解中指定的数据源名称。然后,它会调用 DynamicDataSourceContextHolder
类的 setDataSource
方法来切换数据源。
DynamicDataSourceContextHolder
是 MyBatis-Plus 提供的一个线程安全的上下文工具类,用于保存当前线程使用的数据源名称。
进行数据操作时,会调用org.springframework.jdbc.datasource.getConnection()方法;getConnection()方法最终调用了com.baomidou.dynamic.datasource.ds.AbstractRoutingDataSource的getConnection()方法
上面的determineDataSource是由子类com.baomidou.dynamic.datasource.DynamicRoutingDataSource实现,可以看到DynamicRoutingDataSource从DynamicDataSourceContextHolder获取数据源名称
此时的datasource已经切换成了我们需要的数据源
4、数据操作完成后,方法返回第二步中的拦截器,执行DynamicDataSourceContextHolder.poll();
清除掉此次Threadlocal中的数据源,避免影响后续数据操作。
注意:不可在事务中切换数据库,保证事务需要方法使用同一连接,使用
@DS(dataSourceOne)
方法调用@DS(dataSourceTwo)
无法切换连接,会导致方法报错。
参考文档
<dependency>
<groupId>com.baomidougroupId>
<artifactId>dynamic-datasource-spring-boot-starterartifactId>
<version>${version}version>
dependency>
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
dynamic:
# 设置默认的数据源或者数据源组,默认值即为 master
primary: master
datasource:
# 主库数据源
master:
driverClassName: org.postgresql.Driver
url: jdbc:postgresql://xx.xx.xx.xx:5432/hwaf?currentSchema=common
username: root
password: 123456
# 主库数据源
master1:
driverClassName: org.postgresql.Driver
url: jdbc:postgresql://xx.xx.xx.xx:5432/hwaf?currentSchema=common
username: root
password: 123456
@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。
注解 | 结果 |
---|---|
没有@DS | 默认数据源 |
@DS(“dsName”) | dsName可以为组名也可以为具体某个库的名称 |
@Service
@DS("slave")
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
public List selectAll() {
return jdbcTemplate.queryForList("select * from user");
}
@Override
@DS("slave_1")
public List selectByCondition() {
return jdbcTemplate.queryForList("select * from user where age >10");
}
}
您可以按照以下步骤使用mybatis-plush的dynamic-datasource-spring-boot-starter来根据前端传递的参数动态切换数据源:
在您的Spring Boot应用程序中添加dynamic-datasource-spring-boot-starter依赖项。
在应用程序配置文件中定义数据源配置信息,包括主数据源和其他数据源。
在需要动态切换数据源的方法上使用@DS注释指定数据源,例如:
@DS("#dataSourceName")
public List<User> getUserList(String dataSourceName) {
// 方法实现
}
在这个例子中,#dataSourceName
表示从方法参数中获取数据源名称,并将其用作数据源选择策略。
当调用getUserList方法时,将要使用的数据源名称作为参数传递。例如:
List<User> userList = userService.getUserList("dataSource2");
这将使用名为dataSource2
的数据源来执行getUserList方法。
这个在mybatis-plush官方文档中就有了,接下来说的才是我在开发过程中遇到的问题和解决方案
我想实现的需求是根据传递的参数动态切换数据源,可是我使用的是grpc,没办法像web一样在接口去传递参数
为了改动最小,我打算使用拦截器的方式获取传递的值
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Component
public class DataSourceInterceptor implements ServerInterceptor {
private static final Metadata.Key<String> DATA_SOURCE_KEY =
Metadata.Key.of("data-source", Metadata.ASCII_STRING_MARSHALLER);
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
String dataSource = headers.get(DATA_SOURCE_KEY);
if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSource(dataSource);
}
return Contexts.interceptCall(Context.current(), call, headers, next);
}
}
然后起一个上下文线程去存储这个值
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
CONTEXT_HOLDER.set(dataSource);
}
public static String getDataSource() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSource() {
CONTEXT_HOLDER.remove();
}
}
然后重写DsProcessor对象中的doDetermineDatasource,目的是为了能够获取你存储在DynamicDataSourceContextHolder的数据源参数
@Component
public class DsMetaProcessor extends DsProcessor {
private static final String DATE_PREFIX = "#dataSource";
public DsMetaProcessor() {
}
@Override
public boolean matches(String key) {
return key.startsWith("#dataSource");
}
@Override
public String doDetermineDatasource(MethodInvocation invocation, String key) {
try {
return DynamicDataSourceContextHolder.getDataSource();
} finally {
// 在执行后清理数据源
DynamicDataSourceContextHolder.clearDataSource();
}
}
}
最后在方法接口上通过以下方式注入就可以了
作者:神的孩子都在歌唱
本人博客:https://blog.csdn.net/weixin_46654114
转载说明:务必注明来源,附带本人博客连接。