Multi-Tenancy in Hibernate
AdvancedHibernate supports SCHEMA, DATABASE, and DISCRIMINATOR multi-tenancy strategies; the CurrentTenantIdentifierResolver and ConnectionProvider resolve the tenant per request.
Overview
Multi-tenancy allows a single application instance to serve multiple customers (tenants) with isolated data. Hibernate 6 supports three strategies: SCHEMA (one DB, separate schemas per tenant), DATABASE (separate DB instances per tenant), and DISCRIMINATOR (one schema, tenant_id column on every table — least isolation, simplest ops). The active tenant is resolved per-request via CurrentTenantIdentifierResolver (reads a thread-local or request header). A multi-tenant ConnectionProvider or DataSource router supplies the correct connection. Spring Boot 3 pairs well with AbstractRoutingDataSource for SCHEMA and DATABASE strategies.
Schema-Per-Tenant Strategy
In the SCHEMA strategy, all tenants share one database instance but each has its own schema. Hibernate prepends the schema name to every SQL statement. A TenantContext ThreadLocal holds the current tenant ID for the duration of the request.
// TenantContext — store/clear per request
public class TenantContext {
private static final ThreadLocal<String> CURRENT = new ThreadLocal<>();
public static void set(String tenantId) { CURRENT.set(tenantId); }
public static String get() { return CURRENT.get(); }
public static void clear() { CURRENT.remove(); }
}
// Servlet filter (or Spring Security filter) sets the tenant
@Component
public class TenantFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
String tenant = ((HttpServletRequest) req).getHeader("X-Tenant-Id");
TenantContext.set(tenant);
try { chain.doFilter(req, res); }
finally { TenantContext.clear(); }
}
}
// CurrentTenantIdentifierResolver
@Component
public class TenantIdentifierResolver
implements CurrentTenantIdentifierResolver<String> {
@Override
public String resolveCurrentTenantIdentifier() {
String tenant = TenantContext.get();
return (tenant != null) ? tenant : "public"; // fallback to default schema
}
@Override
public boolean validateExistingCurrentSessions() { return true; }
}AbstractRoutingDataSource for Multi-Tenant Connections
AbstractRoutingDataSource routes to the correct DataSource based on the current tenant key. Each tenant entry maps to its own connection pool. Pair with Hibernate's MultiTenancyStrategy.SCHEMA or DATABASE configuration.
// Multi-tenant DataSource router
public class TenantRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TenantContext.get();
}
}
// Configuration
@Configuration
public class MultiTenantDataSourceConfig {
@Bean
public DataSource dataSource(TenantProperties props) {
Map<Object, Object> sources = new HashMap<>();
for (var entry : props.getTenants().entrySet()) {
sources.put(entry.getKey(), buildHikariDataSource(entry.getValue()));
}
TenantRoutingDataSource router = new TenantRoutingDataSource();
router.setTargetDataSources(sources);
router.setDefaultTargetDataSource(sources.get("public"));
router.afterPropertiesSet();
return router;
}
// application.yml
// multitenancy.tenants.acme.url=jdbc:postgresql://db:5432/acme
// multitenancy.tenants.globex.url=jdbc:postgresql://db:5432/globex
}
// Hibernate config — tell it about multi-tenancy
spring.jpa.properties.hibernate.multiTenancy=SCHEMA
spring.jpa.properties.hibernate.tenant_identifier_resolver=com.example.TenantIdentifierResolverDiscriminator (Row-Level) Multi-Tenancy
The DISCRIMINATOR strategy adds a tenant_id column to every table and Hibernate automatically appends WHERE tenant_id = ? to every query. This offers the simplest ops (single schema, single DB) but weakest isolation — a misconfiguration could expose cross-tenant data.
// Hibernate 6 discriminator strategy — entity annotation
@Entity
@Table(name = "orders")
@TenantId // marks the tenant discriminator field (Hibernate 6.1+)
public class Order {
@Id @GeneratedValue
private Long id;
@TenantId
private String tenantId; // automatically populated from CurrentTenantIdentifierResolver
private OrderStatus status;
private BigDecimal total;
// ...
}
// Hibernate 6 automatically adds tenant filter to all queries:
// SELECT * FROM orders WHERE tenant_id = 'acme' AND id = 42
//
// For bulk updates, the tenant filter is also applied:
// UPDATE orders SET status = 'SHIPPED' WHERE tenant_id = 'acme' AND id IN (...)
// Hibernate configuration
spring.jpa.properties.hibernate.multiTenancy=DISCRIMINATORKey Points to Remember
- 1SCHEMA: shared DB, separate schema per tenant — good isolation, moderate ops complexity.
- 2DATABASE: separate DB instance per tenant — highest isolation, highest resource cost.
- 3DISCRIMINATOR: single schema + tenant_id column — simplest ops, weakest isolation.
- 4CurrentTenantIdentifierResolver resolves the tenant ID per request (e.g. from a header or JWT claim).
- 5AbstractRoutingDataSource routes JDBC connections to the correct tenant data source.
- 6@TenantId (Hibernate 6.1+) marks the discriminator field; Hibernate appends the filter automatically.
Interview Questions
Sign in to ask AriaWhat are the three multi-tenancy strategies in Hibernate and when would you use each?
How does CurrentTenantIdentifierResolver work in a web application?
What are the trade-offs between SCHEMA and DISCRIMINATOR multi-tenancy?
How do you prevent cross-tenant data leaks in the DISCRIMINATOR strategy?
How would you implement tenant-specific connection pools with Spring Boot?
Ask Aria about Multi-Tenancy in Hibernate
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.