Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
113 views
in Technique[技术] by (71.8m points)

java - Adding a new datasource but still pointing to the DefaultTargetDataSource (dynamic multitenant)

I have a spring-boot backend where I have a master database that holds the connection strings for tenant databases, when starting the applciation I make a call to the master database to get the connection info for all tenants and I put them in the target data source, everythign works fine, I can make multiple API calls and the response will be according to the tenant I'm working on, the probleme is when I add a new tenant to the master DB I don't want to restart my backend, so I make another call to the dataSource bean so it gets refreshed, it gets refreshed and the lookup key changes but I get the data from the default data source instead of the one I just added.

I was mainly inspired by this article, here's some pieces of code from what I have

this is the ContextHolder for teh tenant code which I use as a lookup key for the datasource

public final class TenantContextHolder {

    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static final List<String> supportedTenants = new ArrayList<>();

    private TenantContextHolder () {
        super();
    }

    public static void setTenantContext(String tenantCode) {
        threadLocal.set(tenantCode);
    }

    public static String getTenantContext() {
        return threadLocal.get();
    }
}

this is a simple interceptor that I use to check if the tenant doesn't exist already in the supported tenants list, that way I call the dataSource bean once again since it was probable just added to the master DB

public class DataSourceInterceptor extends HandlerInterceptorAdapter {
    private final DBConfig dbConfig;



   @Autowired
    public DataSourceInterceptor(DBConfig dbConfig) {
        this.dbConfig = dbConfig;
    }

   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
           throws Exception {
        if (request.getHeader(TENANT_HEADER_NAME) != null) {
            try {
                String tenantCode= request.getHeader(TENANT_HEADER_NAME);
                TenantContextHolder.setTenantContext(tenantCode);
                if (TenantContextHolder.supportedTenants.contains(tenantCode)) {
                    return super.preHandle(request, response, handler);
                } else {
                    log.error("tenant provided {} not supported, trying to refresh dataSource list ...", request.getHeader(TENANT_HEADER_NAME));
                    dbConfig.dataSource();
                    if (TenantContextHolder.supportedTenants.contains(tenantCode)) {
                        return super.preHandle(request, response, handler);
                    }
                    log.error("Tenant provided {} is still not supported, can't proceed to datasource",
                            request.getHeader(TENANT_HEADER_NAME));
                    throw new HttpCustomException(SOME_CODE);
                }
            } catch (IllegalArgumentException e) {
                log.error("Tenant provided not valid, can't proceed to datasource", e);
                throw new HttpCustomException(SOME_CODE);
            }
        } else {
            log.error("No tenant provided, can't proceed to datasource");
            throw new HttpCustomException(SOME_CODE); }
        }
    }

and this is my dataSource bean

    @Bean()
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public DataSource dataSource() {
        TenantContextHolder.supportedTenants.clear();
        
        List<DataSourceInfo> dataSourcesInfo = getTenantDataSourceInfoFromMasterDB();
        TenantContextHolder.supportedTenants.addAll(dataSourcesInfo.stream().map(DataSourceInfo::getTenantCode).collect(Collectors.toList()));
        CustomRoutingDataSource customDataSource = new CustomRoutingDataSource();

        customDataSource.setTargetDataSources(tenantsDataSource);
        customDataSource.setDefaultTargetDataSource(tenantsDataSource.get(dataSourcesInfo.get(0).getTenantCode()));//i'm aware of the 0 here it's just the default datasource, for when i don't have a lookup key or when it's not found
        return customDataSource;
    }

and finally this is the structure for my DataSourceInfo entity that holds the connection info

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class DataSourceInfo {
    Integer id;
    private String url;
    private String username;
    private String password;
    private String driverClassName;
    private String tenantCode;
    ...
}

as I said, whenever I restart the app everything works great but when I add a new tenant in the master DB, and I make an API call for this tenant, I'll get a log that says it doesn't exist and then it'll call the bean again and it will add this new tenant to the target datasource but the response will always be the first one I set as a default, there is no caching involved so it's definitely not that and even if there is, the caching is different for every tenant

question from:https://stackoverflow.com/questions/65877028/adding-a-new-datasource-but-still-pointing-to-the-defaulttargetdatasource-dynam

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...