diff --git a/persistence-service-v1/persistence-service-internal-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/internal/resources/TenantsResource.java b/persistence-service-v1/persistence-service-internal-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/internal/resources/TenantsResource.java index f64255051..158f862a2 100644 --- a/persistence-service-v1/persistence-service-internal-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/internal/resources/TenantsResource.java +++ b/persistence-service-v1/persistence-service-internal-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/internal/resources/TenantsResource.java @@ -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 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); diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/DynamicDataSourceBasedMultiTenantConnectionProvider.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/DynamicDataSourceBasedMultiTenantConnectionProvider.java index 66d62f68a..da18664ea 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/DynamicDataSourceBasedMultiTenantConnectionProvider.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/DynamicDataSourceBasedMultiTenantConnectionProvider.java @@ -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 tenantDataSources; + private LoadingCache tenantSchemaDataSources; @PostConstruct protected void createCache() { - tenantDataSources = CacheBuilder.newBuilder() + tenantSchemaDataSources = CacheBuilder.newBuilder() .maximumSize(maximumSize) .expireAfterAccess(expireAfterAccess, TimeUnit.MINUTES) - .removalListener((RemovalListener) removal -> { - HikariDataSource ds = (HikariDataSource) removal.getValue(); - ds.close(); // tear down properly - log.info("Closed datasource: {}", ds.getPoolName()); + .removalListener((RemovalListener) 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() { - 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); + } + } diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/repository/SchemaDataSource.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/repository/SchemaDataSource.java new file mode 100644 index 000000000..e484700c0 --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/repository/SchemaDataSource.java @@ -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; + +} diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/utils/jdbc/JDBCUtils.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/utils/jdbc/JDBCUtils.java index b63c27994..9d5ed8b16 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/utils/jdbc/JDBCUtils.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/utils/jdbc/JDBCUtils.java @@ -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();