Jay
2024-10-14 441d3055fb0876835050d52123808b1c46a53179
提交 | 用户 | 时间
d45017 1 package com.iailab.framework.tenant.core.db.dynamic;
2
3 import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
4 import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
5 import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
6 import com.baomidou.dynamic.datasource.processor.DsProcessor;
441d30 7 import com.iailab.framework.tenant.core.context.DataContextHolder;
d45017 8 import com.iailab.framework.tenant.core.context.TenantContextHolder;
9 import com.iailab.framework.tenant.core.service.TenantFrameworkService;
441d30 10 import com.iailab.module.infra.api.db.DataSourceConfigServiceApi;
J 11 import com.iailab.module.infra.api.db.dto.DataSourceConfigRespDTO;
d45017 12 import lombok.RequiredArgsConstructor;
13 import org.aopalliance.intercept.MethodInvocation;
14 import org.springframework.context.annotation.Lazy;
15
16 import javax.annotation.Resource;
17 import javax.sql.DataSource;
18 import java.util.Objects;
19
20 /**
21  * 基于 {@link TenantDS} 的数据源处理器
22  *
23  * 1. 如果有 @TenantDS 注解,返回该租户的数据源
24  * 2. 如果该租户的数据源未创建,则进行创建
25  *
26  * @author 芋道源码
27  */
28 @RequiredArgsConstructor
29 public class TenantDsProcessor extends DsProcessor {
30
31     /**
32      * 用于获取租户数据源配置的 Service
33      */
34     @Resource
35     @Lazy
36     private TenantFrameworkService tenantFrameworkService;
37
38     /**
39      * 动态数据源
40      */
41     @Resource
42     @Lazy  // 为什么添加 @Lazy 注解?因为它和 DynamicRoutingDataSource 相互依赖,导致无法初始化
43     private DynamicRoutingDataSource dynamicRoutingDataSource;
44
45     /**
46      * 用于创建租户数据源的 Creator
47      */
48     @Resource
49     @Lazy
50     private DefaultDataSourceCreator dataSourceCreator;
51
441d30 52     @Resource
J 53     @Lazy
54     private DataSourceConfigServiceApi dataSourceConfigServiceApi;
55
d45017 56     @Override
57     public boolean matches(String key) {
441d30 58         return Objects.equals(key, TenantDS.KEY) || Objects.equals(key, DataDS.KEY);
d45017 59     }
60
61     @Override
62     public String doDetermineDatasource(MethodInvocation invocation, String key) {
441d30 63         if (DataDS.KEY.equals(key)){
J 64             // 获得数据源配置
65             Long dataSourceId = DataContextHolder.getRequiredDataSourceId();
66             DataSourceConfigRespDTO dataSourceConfigRespDTO = dataSourceConfigServiceApi.getDataSourceConfig(dataSourceId);
67             DataSourceProperty dataSourceProperty = new DataSourceProperty();
68             dataSourceProperty.setPoolName(dataSourceConfigRespDTO.getName());
69             dataSourceProperty.setUrl(dataSourceConfigRespDTO.getUrl());
70             dataSourceProperty.setUsername(dataSourceConfigRespDTO.getUsername());
71             dataSourceProperty.setPassword(dataSourceConfigRespDTO.getPassword());
72             // 创建 or 创建数据源,并返回数据源名字
73             return createDatasourceIfAbsent(dataSourceProperty);
74         }else if(TenantDS.KEY.equals(key)){
75             // 获得数据源配置
76             Long tenantId = TenantContextHolder.getRequiredTenantId();
77             DataSourceProperty dataSourceProperty = tenantFrameworkService.getDataSourceProperty(tenantId);
78             // 创建 or 创建数据源,并返回数据源名字
79             return createDatasourceIfAbsent(dataSourceProperty);
80         }
81         return key;
d45017 82     }
83
84     private String createDatasourceIfAbsent(DataSourceProperty dataSourceProperty) {
85         // 1. 重点:如果数据源不存在,则进行创建
86         if (isDataSourceNotExist(dataSourceProperty)) {
87             // 问题一:为什么要加锁?因为,如果多个线程同时执行到这里,会导致多次创建数据源
88             // 问题二:为什么要使用 poolName 加锁?保证多个不同的 poolName 可以并发创建数据源
89             // 问题三:为什么要使用 intern 方法?因为,intern 方法,会返回一个字符串的常量池中的引用
90             // intern 的说明,可见 https://www.cnblogs.com/xrq730/p/6662232.html 文章
91             synchronized (dataSourceProperty.getPoolName().intern()) {
92                 if (isDataSourceNotExist(dataSourceProperty)) {
93                     DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
94                     dynamicRoutingDataSource.addDataSource(dataSourceProperty.getPoolName(), dataSource);
95                 }
96             }
97         }
98         // 2. 返回数据源的名字
99         return dataSourceProperty.getPoolName();
100     }
101
102     private boolean isDataSourceNotExist(DataSourceProperty dataSourceProperty) {
103         return !dynamicRoutingDataSource.getDataSources().containsKey(dataSourceProperty.getPoolName());
104     }
105
106 }