|
@@ -1,18 +1,52 @@
|
|
|
package cn.iocoder.yudao.framework.tenant.core.db.dynamic;
|
|
|
|
|
|
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
|
|
+import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
|
|
|
+import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
|
|
+import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
|
|
|
import com.baomidou.dynamic.datasource.processor.DsProcessor;
|
|
|
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
|
|
|
+import jodd.util.CollectionUtil;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
import org.aopalliance.intercept.MethodInvocation;
|
|
|
+import org.springframework.context.annotation.Lazy;
|
|
|
|
|
|
+import javax.annotation.Resource;
|
|
|
+import javax.sql.DataSource;
|
|
|
import java.util.Objects;
|
|
|
|
|
|
/**
|
|
|
* 基于 {@link TenantDS} 的数据源处理器
|
|
|
*
|
|
|
+ * 1. 如果有 @TenantDS 注解,返回该租户的数据源
|
|
|
+ * 2. 如果该租户的数据源未创建,则进行创建
|
|
|
+ *
|
|
|
* @author 芋道源码
|
|
|
*/
|
|
|
+@RequiredArgsConstructor
|
|
|
public class TenantDsProcessor extends DsProcessor {
|
|
|
|
|
|
+ /**
|
|
|
+ * 用于获取租户数据源配置的 Service
|
|
|
+ */
|
|
|
+ @Resource
|
|
|
+ @Lazy
|
|
|
+ private TenantFrameworkService tenantFrameworkService;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 动态数据源
|
|
|
+ */
|
|
|
+ @Resource
|
|
|
+ @Lazy // 为什么添加 @Lazy 注解?因为它和 DynamicRoutingDataSource 相互依赖,导致无法初始化
|
|
|
+ private DynamicRoutingDataSource dynamicRoutingDataSource;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 用于创建租户数据源的 Creator
|
|
|
+ */
|
|
|
+ @Resource
|
|
|
+ @Lazy
|
|
|
+ private DefaultDataSourceCreator dataSourceCreator;
|
|
|
+
|
|
|
@Override
|
|
|
public boolean matches(String key) {
|
|
|
return Objects.equals(key, TenantDS.KEY);
|
|
@@ -20,12 +54,33 @@ public class TenantDsProcessor extends DsProcessor {
|
|
|
|
|
|
@Override
|
|
|
public String doDetermineDatasource(MethodInvocation invocation, String key) {
|
|
|
+ // 获得数据源配置
|
|
|
Long tenantId = TenantContextHolder.getRequiredTenantId();
|
|
|
- // TODO 芋艿:临时测试
|
|
|
- if (tenantId != 1) {
|
|
|
- tenantId = 2L;
|
|
|
+ DataSourceProperty dataSourceProperty = tenantFrameworkService.getDataSourceProperty(tenantId);
|
|
|
+ // 创建 or 创建数据源,并返回数据源名字
|
|
|
+ return createDatasourceIfAbsent(dataSourceProperty);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String createDatasourceIfAbsent(DataSourceProperty dataSourceProperty) {
|
|
|
+ // 1. 重点:如果数据源不存在,则进行创建
|
|
|
+ if (isDataSourceNotExist(dataSourceProperty)) {
|
|
|
+ // 问题一:为什么要加锁?因为,如果多个线程同时执行到这里,会导致多次创建数据源
|
|
|
+ // 问题二:为什么要使用 poolName 加锁?保证多个不同的 poolName 可以并发创建数据源
|
|
|
+ // 问题三:为什么要使用 intern 方法?因为,intern 方法,会返回一个字符串的常量池中的引用
|
|
|
+ // intern 的说明,可见 https://www.cnblogs.com/xrq730/p/6662232.html 文章
|
|
|
+ synchronized (dataSourceProperty.getPoolName().intern()) {
|
|
|
+ if (isDataSourceNotExist(dataSourceProperty)) {
|
|
|
+ DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
|
|
|
+ dynamicRoutingDataSource.addDataSource(dataSourceProperty.getPoolName(), dataSource);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- return "tenant_" + tenantId + "_ds";
|
|
|
+ // 2. 返回数据源的名字
|
|
|
+ return dataSourceProperty.getPoolName();
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isDataSourceNotExist(DataSourceProperty dataSourceProperty) {
|
|
|
+ return !dynamicRoutingDataSource.getDataSources().containsKey(dataSourceProperty.getPoolName());
|
|
|
}
|
|
|
|
|
|
}
|