Files
hibernate-demo/multitenancy/README.md
T

48 lines
2.5 KiB
Markdown

# multitenancy
Companion module for the ankurm.com article **Multi-Tenancy with Hibernate 7 and Spring Boot 4: `@TenantId` vs Schema-per-Tenant**
(https://ankurm.com/hibernate-multi-tenancy-spring-boot-tenantid-vs-schema/).
Every claim in the article comes from a test here; each test writes what it observed to `output/`. All tests run against a real
PostgreSQL 17 container started by Testcontainers (needs a Docker daemon).
## Versions
| Component | Version | Notes |
|---|---|---|
| Hibernate ORM | `7.4.14.Final` | newest 7.x GA on Maven Central on 2026-10-29; overrides the `7.4.13.Final` Spring Boot 4.1.2 manages |
| Spring Boot | `4.1.2` | parent POM |
| Spring Security (JOSE only) | `7.1.2` | `spring-security-oauth2-jose`, Nimbus JWT; no security filter chain |
| PostgreSQL / driver | `17.11` / `42.7.14` | `postgres:17-alpine`; Testcontainers `2.0.5` |
| Java | `25` (Temurin 25.0.5+8) | |
## Run
```bash
./scripts/run-all.sh # javap dump + mvn clean test; regenerates everything in output/
```
## Layout
| Package | What |
|---|---|
| `tenant` | `TenantContext` (ThreadLocal), `JwtTenantFilter`, `TenantIdResolver` (Hibernate resolver), `TenantHibernateConfig`, `TenantNames` allow-list |
| `discriminator` | `DiscriminatorApp`, `Invoice` with `@TenantId`, repository, controller |
| `schema` | `SchemaApp`, `Invoice` (no tenant column), `SearchPathConnectionProvider` (shared pool; also the deliberately leaky variant), `PoolPerTenantConnectionProvider`, `SchemaProvisioner` |
## Output
| File | What it shows |
|---|---|
| `00-javap.txt` | `javap` of the tenancy types in the shipped jars; Boot's `HibernatePropertiesCustomizer` package |
| `01-api-shape.txt` | packages, generics and setting names by reflection |
| `02-discriminator.txt` | `@TenantId` DDL and SQL, cross-tenant `findById`, bulk HQL, native-SQL bypass, no-tenant and mismatched-tenant failures |
| `03-schema-per-tenant.txt` | schema-per-tenant on one shared pool: SQL, overlapping ids, ghost tenant, hostile name, pool connection count |
| `04-search-path-leak.txt` | a provider that forgets to reset `search_path` vs the correct one |
| `05-pool-per-tenant.txt` | one Hikari pool per tenant: connections per pool in `pg_stat_activity` |
| `06-http-discriminator.txt` / `07-http-schema.txt` | JWT to tenant over real HTTP: 401 / 403 / 404 / 200 cases |
## Not covered (not run)
Row-level security, database-per-tenant (separate servers), L2 cache with multi-tenancy, Envers, Flyway per schema, JWKS/asymmetric
keys, `@Async` / virtual-thread propagation of the tenant, and any load or latency measurement.