Files
clean-architecture-backend-…/docs/jpa/support-matrix.md
T
DongHyeonkaandClaude Opus 5 0e61f86eb5 feat(jpa): implement the JPA relational persistence platform
Implements the Stable and Experimental JPA persistence platform designs
against real PostgreSQL, adapted to this repository's fail-closed 19-leaf
registry.

The design models the platform as 25 Gradle projects. `src/settings.gradle`
throws unless the registry holds exactly 19 leaves, so the plan's modules
become packages inside `:adapter:outbound:persistence-jpa` (starter in
`:app-bootstrap`, testkit in its own source set). The full mapping, the
renames this repository's naming gate required, and every deliberate
substitution are recorded in `docs/jpa/repository-adaptation.md`.

Seven Docker-backed lanes replace the plan's seven JVM test suites. Each
fails closed: a lane that discovers nothing, or a container that cannot
start, is an error rather than a skip.

Three defects the contracts found against a real server:

- `CommitFailureClassifier` treated only SQLSTATE 40003, class 08, and
  transport breaks as completion-unknown. A backend terminated mid-commit
  reports 57P01, and the commit record may already be in the WAL — so a
  possibly-committed transaction could be re-run. 57P01/57P02/57P03 now
  classify as completion-unknown.
- `SchemaTenantMigrationOrchestrator` recorded `MigrateResult`'s target
  version, which is empty for a tenant already current, reporting migrated
  tenants as unmigrated during a partial rollout. It now reads the applied
  version back from the tenant's schema history.
- `JpaStreamExecutor` checked only the declared return type for reactive
  publishers, and `RegisteredPostgreSqlCopyLoader` passed the COPY timeout
  to `SET`, which is parsed before parameter binding.

`JpaModuleBoundaryTest` enforces the plan's module map as package rules;
`verifyCleanArchitectureDependencies` governs edges between leaves and
cannot see these. Its first assertion is that the import is non-empty,
because every rule under it is a `noClasses()` rule and would pass
vacuously on an empty import.

Verified: 128 container tests across all seven lanes, 1183 unit tests,
`:adapter:outbound:persistence-jpa:check`, `:app-bootstrap:check`,
`verifyCleanArchitectureDependencies`, `verifyOneTypePerFile`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 14:06:18 +09:00

80 lines
3.6 KiB
Markdown

# JPA Persistence Platform — Support Matrix
The machine-readable source for `JpaReleaseManifest`. A release gate parses this file, so a version
or gate that stops being named here stops being claimed — and the build fails rather than the
document quietly drifting from the code.
## Database
| Database | Support | Evidence |
|---|---|---|
| PostgreSQL 16 | Stable | full contract suite, release lane |
| PostgreSQL 17 | Stable | full contract suite, release lane |
| PostgreSQL 18 | Stable | full contract suite, release lane |
| PostgreSQL 19 | Experimental | compatibility lane only; promotion requires an ADR |
| H2 | Local convenience | **never** evidence of PostgreSQL behaviour |
H2 is not a second production target. It reports different SQLSTATEs for the same violation, has no
`SKIP LOCKED` guarantee the platform relies on, no JSONB operators, no range types, and no
concurrent index builds. A green H2 run is evidence that the code compiles and runs, and nothing
more.
## Specification and provider
| Component | Stable | Experimental |
|---|---|---|
| Jakarta Persistence | 3.2 | 4.0 (lane) |
| Hibernate ORM | 7.4 declared baseline | 8 (lane) |
| Spring Boot | repository BOM | — |
The Hibernate row needs a note. The design declares 7.4 as the Stable provider; this repository's
Spring Boot BOM resolves 7.1.x. `HibernateProviderPolicy` holds both — the declared baseline as a
constant, the resolved version read from Hibernate itself — and `driftsFromDeclaredBaseline()` makes
the difference visible instead of asserting a constant against itself. See
[repository-adaptation.md](repository-adaptation.md) §4.
## Capability support levels
| Capability | Level |
|---|---|
| Full-transaction retry | Stable |
| Commit completion evidence | Stable |
| Keyset pagination | Stable |
| JDBC batch | Stable |
| Flyway schema gate | Stable |
| Runtime role verification | Stable |
| Observability | Stable |
| PostgreSQL native write (`ON CONFLICT`/`RETURNING`) | Advanced |
| PostgreSQL work claim (`SKIP LOCKED`) | Advanced |
| PostgreSQL JSONB | Advanced |
| PostgreSQL array and range | Advanced |
| Bulk DML | Advanced |
| Hibernate `StatelessSession` | Advanced |
| PostgreSQL `COPY` | Admin (J4) |
| Hibernate second-level cache | Advanced |
| Hibernate Envers | Advanced |
| Multi-tenancy (column, RLS, schema, database) | Experimental |
| Consistency-aware read replica | Experimental |
## Release gates
Each row is a way the platform could pass its tests and still be wrong in production.
| Gate | Kind | What it prevents |
|---|---|---|
| `postgresql-contract` | gate | a release whose only database evidence came from H2 |
| `completion-unknown-no-retry` | gate | automatically re-running a write that may already have committed |
| `osiv-disabled` | gate | lazy loading from the view layer, one query per rendered row |
| `flyway-validate` | gate | Hibernate mutating a deployed schema, or running against one it was not built for |
| `runtime-role-no-ddl` | gate | the application's own credential being able to alter or drop schema objects |
| `hibernate-7.4-fetch-pagination` | gate | a paged collection fetch silently reading the whole table and paginating in memory |
## Explicitly unsupported
- Reactive JPA. JPA is a blocking specification; a reactive facade over it moves the blocking call
onto an event loop rather than removing it.
- Hibernate as the production schema writer. `ddl-auto` never mutates a deployed schema.
- A platform-owned generic CRUD repository. Domains own their repositories (design §10.1).
- Automatic reconciliation of a completion-unknown transaction. The platform records; the domain
resolves.