Add multitenancy module: Hibernate 7 @TenantId vs schema-per-tenant (shared pool + search_path, pool per tenant), JWT tenant filter, isolation failure modes on PostgreSQL 17 (Hibernate 7.4.14.Final, Spring Boot 4.1.2)
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b4VQVEJWetTT1dYzyJ
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user