houzhongjian
2025-04-10 152781b05131e48bf6e94d71cc72dd54af52a3fb
iailab-framework/iailab-common-biz-tenant/src/main/java/com/iailab/framework/tenant/core/db/dynamic/TenantDsProcessor.java
对比新文件
@@ -0,0 +1,106 @@
package com.iailab.framework.tenant.core.db.dynamic;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.processor.DsProcessor;
import com.iailab.framework.tenant.core.context.DataContextHolder;
import com.iailab.framework.tenant.core.context.TenantContextHolder;
import com.iailab.framework.tenant.core.service.TenantFrameworkService;
import com.iailab.module.infra.api.db.DataSourceConfigServiceApi;
import com.iailab.module.infra.api.db.dto.DataSourceConfigRespDTO;
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;
    @Resource
    @Lazy
    private DataSourceConfigServiceApi dataSourceConfigServiceApi;
    @Override
    public boolean matches(String key) {
        return Objects.equals(key, TenantDS.KEY) || Objects.equals(key, DataDS.KEY);
    }
    @Override
    public String doDetermineDatasource(MethodInvocation invocation, String key) {
        if (DataDS.KEY.equals(key)){
            // 获得数据源配置
            Long dataSourceId = DataContextHolder.getRequiredDataSourceId();
            DataSourceConfigRespDTO dataSourceConfigRespDTO = dataSourceConfigServiceApi.getDataSourceConfig(dataSourceId);
            DataSourceProperty dataSourceProperty = new DataSourceProperty();
            dataSourceProperty.setPoolName(dataSourceConfigRespDTO.getName());
            dataSourceProperty.setUrl(dataSourceConfigRespDTO.getUrl());
            dataSourceProperty.setUsername(dataSourceConfigRespDTO.getUsername());
            dataSourceProperty.setPassword(dataSourceConfigRespDTO.getPassword());
            // 创建 or 创建数据源,并返回数据源名字
            return createDatasourceIfAbsent(dataSourceProperty);
        }else if(TenantDS.KEY.equals(key)){
            // 获得数据源配置
            Long tenantId = TenantContextHolder.getRequiredTenantId();
            DataSourceProperty dataSourceProperty = tenantFrameworkService.getDataSourceProperty(tenantId);
            // 创建 or 创建数据源,并返回数据源名字
            return createDatasourceIfAbsent(dataSourceProperty);
        }
        return key;
    }
    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);
                }
            }
        }
        // 2. 返回数据源的名字
        return dataSourceProperty.getPoolName();
    }
    private boolean isDataSourceNotExist(DataSourceProperty dataSourceProperty) {
        return !dynamicRoutingDataSource.getDataSources().containsKey(dataSourceProperty.getPoolName());
    }
}