dengzedong
2024-10-14 3e18d4bfbf2c657b08b21512c2d884cc9d59df7b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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());
    }
 
}