java
56 lines · 7 steps
Multi-tenant row filtering with Hibernate in Spring
Enforce per-tenant data isolation automatically using a Hibernate filter toggled by a Spring interceptor on every request.
Explained by
highlit
1@Entity
2@Table(name = "invoices")
3@FilterDef(name = "tenantFilter", parameters = @ParamDef(name = "tenantId", type = String.class))
4@Filter(name = "tenantFilter", condition = "tenant_id = :tenantId")
5public class Invoice {
6
7 @Id
8 @GeneratedValue(strategy = GenerationType.UUID)
9 private UUID id;
10
11 @Column(name = "tenant_id", nullable = false, updatable = false)
12 private String tenantId;
13
14 @Column(nullable = false)
15 private BigDecimal amount;
16
17 @Enumerated(EnumType.STRING)
18 private InvoiceStatus status;
19}
20
21@Component
22@RequiredArgsConstructor
23public class TenantFilterInterceptor implements HandlerInterceptor {
24
25 private final EntityManager entityManager;
26
27 @Override
28 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
29 String tenantId = TenantContext.getTenantId();
30 if (tenantId == null) {
31 response.setStatus(HttpStatus.FORBIDDEN.value());
32 return false;
33 }
34 entityManager.unwrap(Session.class)
35 .enableFilter("tenantFilter")
36 .setParameter("tenantId", tenantId);
37 return true;
38 }
39
40 @Override
41 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
42 entityManager.unwrap(Session.class).disableFilter("tenantFilter");
43 }
44}
45
46@Configuration
47@RequiredArgsConstructor
48public class WebConfig implements WebMvcConfigurer {
49
50 private final TenantFilterInterceptor tenantFilterInterceptor;
51
52 @Override
53 public void addInterceptors(InterceptorRegistry registry) {
54 registry.addInterceptor(tenantFilterInterceptor).addPathPatterns("/api/**");
55 }
56}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Hibernate filters let you inject a WHERE clause into every query without touching individual repositories.
- 2Enabling the filter per-request and disabling it afterward keeps tenant scoping tied to the request lifecycle and avoids leaking state across pooled connections.
- 3Centralizing tenant enforcement in an interceptor removes the risk of forgetting the tenant check in any single query.
Related explainers
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
java
package com.acme.billing.config; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties;
Feature-flagged beans with Spring @ConditionalOnProperty
feature-flags
conditional-beans
strategy-pattern
Intermediate
5 steps
java
public static Map<String, String> parseCookieHeader(String header) { Map<String, String> cookies = new LinkedHashMap<>(); if (header == null || header.isBlank()) { return cookies;
Parsing an HTTP Cookie header in Java
string-parsing
http
url-decoding
Intermediate
6 steps
java
public class TimedSocketReader { private static final int READ_TIMEOUT_MS = 5_000; private static final int CONNECT_TIMEOUT_MS = 3_000;
Reading a socket with connect and read timeouts
sockets
timeouts
io
Intermediate
8 steps
typescript
import { Injectable, Scope, Inject, NotFoundException } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { Request } from 'express'; import { DataSource } from 'typeorm';
Per-tenant database connections in NestJS
multi-tenancy
connection-pooling
dependency-injection
Advanced
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/multi-tenant-row-filtering-with-hibernate-in-spring-explained-java-0cc5/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.