RED-5481: Reuse datasource if only schema is different
This commit is contained in:
parent
d3b0ce243c
commit
246cc0d6de
@ -1,6 +1,5 @@
|
||||
package com.iqser.red.service.persistence.service.v1.api.internal.resources;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
@ -18,7 +17,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenanc
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
public interface TenantsResource {
|
||||
|
||||
String TENANT_PATH ="/tenants";
|
||||
String TENANT_PATH = "/tenants";
|
||||
String TENANT_ID_PARAM = "tenantId";
|
||||
String TENANT_ID_PATH_PARAM = "/{" + TENANT_ID_PARAM + "}";
|
||||
|
||||
@ -31,7 +30,7 @@ public interface TenantsResource {
|
||||
List<TenantResponse> getTenants();
|
||||
|
||||
|
||||
@GetMapping(value = InternalApi.BASE_PATH + TENANT_PATH+ TENANT_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(value = InternalApi.BASE_PATH + TENANT_PATH + TENANT_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
TenantResponse getTenant(@PathVariable(TENANT_ID_PARAM) String tenantId);
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -17,6 +19,7 @@ import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.cache.RemovalListener;
|
||||
import com.iqser.red.service.persistence.management.v1.processor.multitenancy.entity.TenantEntity;
|
||||
import com.iqser.red.service.persistence.management.v1.processor.service.EncryptionDecryptionService;
|
||||
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.SchemaDataSource;
|
||||
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.TenantRepository;
|
||||
import com.iqser.red.service.persistence.management.v1.processor.utils.jdbc.JDBCUtils;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
@ -42,31 +45,50 @@ public class DynamicDataSourceBasedMultiTenantConnectionProvider extends Abstrac
|
||||
@Value("${multitenancy.datasource-cache.expireAfterAccess:10}")
|
||||
private Integer expireAfterAccess;
|
||||
|
||||
private LoadingCache<String, DataSource> tenantDataSources;
|
||||
private LoadingCache<String, SchemaDataSource> tenantSchemaDataSources;
|
||||
|
||||
|
||||
@PostConstruct
|
||||
protected void createCache() {
|
||||
|
||||
tenantDataSources = CacheBuilder.newBuilder()
|
||||
tenantSchemaDataSources = CacheBuilder.newBuilder()
|
||||
.maximumSize(maximumSize)
|
||||
.expireAfterAccess(expireAfterAccess, TimeUnit.MINUTES)
|
||||
.removalListener((RemovalListener<String, DataSource>) removal -> {
|
||||
HikariDataSource ds = (HikariDataSource) removal.getValue();
|
||||
ds.close(); // tear down properly
|
||||
log.info("Closed datasource: {}", ds.getPoolName());
|
||||
.removalListener((RemovalListener<String, SchemaDataSource>) removal -> {
|
||||
|
||||
var toRemove = removal.getValue();
|
||||
int numberOfSchemasForSameDataSource = 0;
|
||||
for (var schemaDataSource : tenantSchemaDataSources.asMap().values()) {
|
||||
if (toRemove.getJdbcUrl().equals(schemaDataSource.getJdbcUrl())) {
|
||||
numberOfSchemasForSameDataSource++;
|
||||
}
|
||||
}
|
||||
if (numberOfSchemasForSameDataSource <= 1) {
|
||||
HikariDataSource ds = (HikariDataSource) removal.getValue().getDataSource();
|
||||
ds.close(); // tear down properly
|
||||
log.info("Closed datasource: {}", ds.getPoolName());
|
||||
} else {
|
||||
log.info("Keeping datasource open from {} because it is still used by {} other tenants", removal.getKey(), numberOfSchemasForSameDataSource - 1);
|
||||
}
|
||||
})
|
||||
.build(new CacheLoader<String, DataSource>() {
|
||||
public DataSource load(String key) {
|
||||
.build(new CacheLoader<>() {
|
||||
public SchemaDataSource load(String key) {
|
||||
|
||||
TenantEntity tenant = masterTenantRepository.findByTenantId(key).orElseThrow(() -> new RuntimeException("No such tenant: " + key));
|
||||
var jdbcUrl = JDBCUtils.buildJdbcUrl(tenant.getDatabaseConnection());
|
||||
|
||||
for (var schemaDataSource : tenantSchemaDataSources.asMap().values()) {
|
||||
if (schemaDataSource.getJdbcUrl().equals(jdbcUrl)) {
|
||||
return new SchemaDataSource(tenant.getDatabaseConnection().getSchema(), schemaDataSource.getDataSource(), jdbcUrl);
|
||||
}
|
||||
}
|
||||
return createAndConfigureDataSource(tenant);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private DataSource createAndConfigureDataSource(TenantEntity tenant) {
|
||||
private SchemaDataSource createAndConfigureDataSource(TenantEntity tenant) {
|
||||
|
||||
String decryptedPassword = encryptionService.decrypt(tenant.getDatabaseConnection().getPassword());
|
||||
|
||||
@ -75,13 +97,13 @@ public class DynamicDataSourceBasedMultiTenantConnectionProvider extends Abstrac
|
||||
ds.setUsername(tenant.getDatabaseConnection().getUsername());
|
||||
ds.setPassword(decryptedPassword);
|
||||
|
||||
// TODO Should not include schema
|
||||
ds.setJdbcUrl(JDBCUtils.buildJdbcUrl(tenant.getDatabaseConnection()));
|
||||
|
||||
ds.setPoolName(tenant.getTenantId() + TENANT_POOL_NAME_SUFFIX);
|
||||
var jdbcUrl = JDBCUtils.buildJdbcUrl(tenant.getDatabaseConnection());
|
||||
ds.setJdbcUrl(jdbcUrl);
|
||||
ds.setSchema(tenant.getDatabaseConnection().getSchema());
|
||||
ds.setPoolName(jdbcUrl + TENANT_POOL_NAME_SUFFIX);
|
||||
|
||||
log.info("Configured datasource: {}", ds.getPoolName());
|
||||
return ds;
|
||||
return new SchemaDataSource(tenant.getDatabaseConnection().getSchema(), ds, jdbcUrl);
|
||||
}
|
||||
|
||||
|
||||
@ -96,10 +118,32 @@ public class DynamicDataSourceBasedMultiTenantConnectionProvider extends Abstrac
|
||||
public DataSource selectDataSource(String tenantIdentifier) {
|
||||
|
||||
try {
|
||||
return tenantDataSources.get(tenantIdentifier);
|
||||
return tenantSchemaDataSources.get(tenantIdentifier).getDataSource();
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("Failed to load DataSource for tenant: " + tenantIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Connection getConnection(String tenantIdentifier) throws SQLException {
|
||||
|
||||
try {
|
||||
var schemaDataSource = tenantSchemaDataSources.get(tenantIdentifier);
|
||||
Connection connection = schemaDataSource.getDataSource().getConnection();
|
||||
connection.setSchema(schemaDataSource.getSchema());
|
||||
return connection;
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("No such tenant: " + tenantIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
|
||||
|
||||
connection.setSchema(null);
|
||||
releaseAnyConnection(connection);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class SchemaDataSource {
|
||||
|
||||
private String schema;
|
||||
private DataSource dataSource;
|
||||
private String jdbcUrl;
|
||||
|
||||
}
|
||||
@ -17,11 +17,10 @@ public class JDBCUtils {
|
||||
.append(':')
|
||||
.append(databaseConnectionEntity.getPort())
|
||||
.append('/')
|
||||
.append(databaseConnectionEntity.getDatabase())
|
||||
.append('?')
|
||||
.append("currentSchema=")
|
||||
.append(databaseConnectionEntity.getSchema());
|
||||
.append(databaseConnectionEntity.getDatabase());
|
||||
|
||||
if(databaseConnectionEntity.getParams() != null) {
|
||||
sb.append('?');
|
||||
databaseConnectionEntity.getParams().forEach((k, v) -> sb.append('&').append(k).append(v));
|
||||
}
|
||||
return sb.toString();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user