动态数据源
多数据我们进行的相关的封装,支持根据 header 切换数据源功能,如果你是开发人员请务必阅读 数据源通用库文档 来理解数据源的原理,这可以免除开发中的很多麻烦。
自动切换数据源
数据源拦截器主要作用是自动切换数据源,如果不需要这个拦截器,需要手动切换数据源,则需要配置在 application.yml 中。
重要
如果启用,则会通过 header
中的 Tenant-Sn
自动切换数据源,如果没有 Tenant-Sn
会返回 403
。切记这个过滤器是在业务应用中,并非 gateway
中。
simperfect:
datasource:
dynamic:
datasource-interceptor-enabled: false # 是否启用数据源拦截器,登录模块不需要过滤器,关闭即可
自动切换数据源拦截器白名单
在没有需要任何数据源信息、或者需要自己管理数据源的情况下可以添加到白名单中,这样请求过来就不会被拦截器拦截。这个配置适合在有少数接口需要自己处理数据源时是用。
simperfect:
datasource:
dynamic:
interceptor-exclude-path-patterns:
- '/*/public-resources/**'
- '/*/*/public-resources/**'
手动切换数据源
提示
可以使用 DataSourceTaskUtils
工具类来切换数据源并执行任务,具体使用方法可以查看方法注释。
有些场景下我们需要手动切换数据源,如多租户下的定时任务、登录模块、管理配置模块等等
/**
* @author 王金城
* @date 2022/9/26
**/
@Component
@EnableScheduling
public class TestDynamicDataSourceScheduled {
@Resource
private IEmployeeService employeeService;
private static final Logger logger = LoggerFactory.getLogger(TestDynamicDataSourceScheduled.class);
@Scheduled(fixedDelay = 1000 * 60)
public void dynamicDataSource() {
Set<String> tenantSNs = DataSourceUtils.getAllTenantSN();
if (CollectionUtils.isEmpty(tenantSNs)) {
return;
}
for (String tenantSN : tenantSNs) {
try {
DynamicDataSourceContextHolder.push(tenantSN);
List<BpEmployee> bpEmployees = employeeService.queryAllEmployee();
logger.info("员工数据 {}", JSON.toJSONString(bpEmployees));
} catch (Exception e) {
logger.error("定时任务异常", e);
} finally {
// 必须在finally中清除数据源
DynamicDataSourceContextHolder.clear();
}
}
}
}
注意
切换数据源后,一定要记得在合适的位置调用 DynamicDataSourceContextHolder.clear()
清空当前线程的数据源信息。
如果是多次切换数据源,需要一层一层清除数据库可以是用 DynamicDataSourceContextHolder.poll()
方法。
- 清除数据源务必写在
finally
中,防止程序异常无法执行 - 在本次逻辑(线程)结束时再清除
- 如果开启事务,那么将无法切换数据源
数据源监听器
警告
多租户模式下,数据初始化一定要使用 Listener
来处理,禁止使用 runner
来处理多租户数据,因为它仅仅是程序启动时运行,无法监听租户变化。
有些场景需要监听数据源的变化,例如在管理本地缓存是,故内置了一个监听器 IDynamicDataSourceListener
接口,实现后注入 bean
即可进行监听数据源变化。
/**
* 数据源变化监听,只有再启用多数据的时候才会加载
*
* @author 王金城
* @date 2024/1/31
**/
@Component
@ConditionalOnProperty(prefix = "simperfect.datasource.dynamic", name = "enabled", havingValue = "true", matchIfMissing = true)
public class DynamicDataSourceListener implements IDynamicDataSourceListener {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceListener.class);
@Resource
private IBpConfigurationService bpConfigurationService;
@Override
public void datasourceChanged(DataSourceChangedInfo data) {
// 只检测新增的数据源数据
if (CollectionUtils.isEmpty(data.getAddKeys())) {
logger.info("检查租户配置数据 - 无需检查,因为无新增数据源");
return;
}
for (String tenantSN : data.getAddKeys()) {
String tenantId = DataSourceUtils.getTenantId(tenantSN);
logger.info("检查租户 [{}] 配置数据 - 开始", tenantId);
try {
DynamicDataSourceContextHolder.push(tenantSN);
bpConfigurationService.checkDataHandler();
} catch (Exception e) {
logger.error("检查租户 [{}] 配置数据时发生异常", tenantId, e);
} finally {
DynamicDataSourceContextHolder.poll();
}
logger.info("检查租户 [{}] 配置数据 - 完成", tenantId);
}
}
}
自动更新数据源
数据源自动更新服务主要作用是自动更新数据源,如果不需要这个服务,则需要手动更新数据源,则需要配置在 application.yml 中。
simperfect:
datasource:
dynamic:
auto-update-enabled: true # 是否启用多数据自动更新服务,一般程序都需要自动更新数据源。例如:租户管理台就不需要开启,因为它需要手动维护数据源