Shared Schema Multitenancy in Hibernate 6: A Practical Guide
1. Introduction
Multi-tenancy lets a single application serve multiple clients (tenants) while keeping data isolated. In shared schema multitenancy, all tenants share the same database schema but use a discriminator column or row-level filtering to separate data. Hibernate 6 simplifies this approach, eliminating older configurations like MultiTenancyStrategy
and introducing streamlined setups.
2. Usages
- SaaS Applications: Manage customer data securely in a single database.
- Regulatory Compliance: Isolate sensitive data without separate schemas.
- Cost Efficiency: Reduce infrastructure costs compared to separate databases per tenant.
- Scalability: Add tenants without schema duplication.
3. Code Example
Step 1: Define Tenant Resolver
@Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {
@Override
public String resolveCurrentTenantIdentifier() {
return Optional.ofNullable(TenantContext.getCurrentTenant())
.orElse("default_tenant");
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
Step 2: Configure Connection Provider
@Component
public class SchemaConnectionProvider implements MultiTenantConnectionProvider {
@Autowired
private DataSource dataSource;
@Override
public Connection getAnyConnection() throws SQLException {
return dataSource.getConnection();
}
@Override
public Connection getConnection(String tenantId) throws SQLException {
Connection connection = getAnyConnection();
connection.setSchema(tenantId); // Switch schema dynamically
return connection;
}
@Override
public void releaseConnection(String tenantId, Connection connection) throws SQLException {
connection.setSchema("public"); // Reset to default schema
connection.close();
}
}
Step 3: Enable Multi-tenancy in Hibernate
@Configuration
public class HibernateConfig {
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
DataSource dataSource,
MultiTenantConnectionProvider connectionProvider,
CurrentTenantIdentifierResolver tenantResolver) {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.multi_tenant_connection_provider", connectionProvider);
properties.put("hibernate.tenant_identifier_resolver", tenantResolver);
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter())
.dataSource(dataSource)
.packages("com.example.model")
.properties(properties)
.build();
}
}
4. Explanation
- Tenant Resolver: Identifies the current tenant (e.g., from HTTP headers or JWT claims).
- Connection Provider: Switches schemas at runtime using
connection.setSchema()
. - Schema Isolation: Each tenant's data resides in a separate schema but within the same physical database.
5. Best Practices
- ThreadLocal Storage: Store tenant IDs in
ThreadLocal
to avoid leaks across requests. - Schema Validation: Ensure schemas exist before switching (e.g., via Liquibase/Flyway).
- Caching: Use tenant-specific caches to prevent cross-tenant data leaks.
- Connection Pooling: Configure separate pools per tenant for high throughput.
6. Conclusion
Hibernate 6's shared schema multitenancy simplifies tenant isolation without complex infrastructure. By dynamically switching schemas and resolving tenants per request, you can build scalable SaaS applications efficiently.
Tags:
hibernate