Я сделал проект, в котором я могу создать несколько источников данных с вашими конкретными наборами изменений, поэтому, если вам нужно добавить другой источник данных, это просто изменит ваш application.yml, больше не нужно менять код.
Но если не использовать, просто удалите работающую жидкость!
При каждом обращении к контроллеру вам необходимо получить заголовок X-TenantId, который изменит ваш ThreadLocal, что, в свою очередь, изменит источник данных в соответствии с владельцем
Код завершен: https://github.com/dijalmasilva/spring-boot-multitenancy-datasource-liquibase
application.yml
spring:
dataSources:
- tenantId: db1
url: jdbc:postgresql://localhost:5432/db1
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
liquibase:
enabled: true
default-schema: public
change-log: classpath:db/master/changelog/db.changelog-master.yaml
- tenantId: db2
url: jdbc:postgresql://localhost:5432/db2
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
- tenantId: db3
url: jdbc:postgresql://localhost:5432/db3
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
TenantContext
public class TenantContext {
private static ThreadLocal<String> currentTenant = new ThreadLocal<>();
static String getCurrentTenant() {
return currentTenant.get();
}
static void setCurrentTenant(String tenant) {
currentTenant.set(tenant);
}
static void clear() {
currentTenant.remove();
}
}
Фильтр для контроллеров
public class TenantFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
final String X_TENANT_ID = "X-TenantID";
final HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
final String tenantId = httpServletRequest.getHeader(X_TENANT_ID);
if (tenantId == null) {
final HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write("{\"error\": \"No tenant header supplied\"}");
response.getWriter().flush();
TenantContext.clear();
return;
}
TenantContext.setCurrentTenant(tenantId);
filterChain.doFilter(servletRequest, servletResponse);
}
}
Класс конфигурации, если используется жидкостная база
@Configuration
@ConditionalOnProperty(prefix = "spring.liquibase", name = "enabled", matchIfMissing = true)
@EnableConfigurationProperties(LiquibaseProperties.class)
@AllArgsConstructor
public class LiquibaseConfiguration {
private LiquibaseProperties properties;
private DataSourceProperties dataSourceProperties;
@Bean
@DependsOn("tenantRoutingDataSource")
public MultiTenantDataSourceSpringLiquibase liquibaseMultiTenancy(Map<Object, Object> dataSources,
@Qualifier("taskExecutor") TaskExecutor taskExecutor) {
// to run changeSets of the liquibase asynchronous
MultiTenantDataSourceSpringLiquibase liquibase = new MultiTenantDataSourceSpringLiquibase(taskExecutor);
dataSources.forEach((tenant, dataSource) -> liquibase.addDataSource((String) tenant, (DataSource) dataSource));
dataSourceProperties.getDataSources().forEach(dbProperty -> {
if (dbProperty.getLiquibase() != null) {
liquibase.addLiquibaseProperties(dbProperty.getTenantId(), dbProperty.getLiquibase());
}
});
liquibase.setContexts(properties.getContexts());
liquibase.setChangeLog(properties.getChangeLog());
liquibase.setDefaultSchema(properties.getDefaultSchema());
liquibase.setDropFirst(properties.isDropFirst());
liquibase.setShouldRun(properties.isEnabled());
return liquibase;
}
}
Код завершен: https://github.com/dijalmasilva/spring-boot-multitenancy-datasource-liquibase