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()); } }