feat(data): actually invoke TxSynchronization.beforeCommit, and document the contract
beforeCommit(boolean) has been part of the TxSynchronization API from the start and was invoked by nothing, in either manager — anyone implementing it got silence, the same failure class as the dropped afterCommit callbacks in the previous commit. Left out of that one because fixing it is a design decision rather than a restored behaviour; this is that decision, written down. It now runs immediately before the real commit, with the transaction still active and its session/connection still bound, which is the whole reason to have a hook on this side of the commit: it can still write through the same resource and land in the same atomic unit. Skipped when the transaction is already rollback-only, since there is no commit to precede. Throwing from it vetoes the commit: the transaction rolls back, the surviving callbacks hear ROLLED_BACK, and the exception propagates. Without that, a hook running before the commit would be strictly less useful than one running after. A commit that fails on its own now takes the same path instead of completing silently with no callback at all, and a rollback that also fails is attached as a suppressed exception rather than replacing the one that explains the failure. Documented on the interface itself and in flash-ext-data-core/docs/README.md: which hook sits on which side of the commit, what each may still touch, what throwing does, and the per-transaction scoping rule from the previous commit. Tests: 6 more (3 per manager) — runs inside the transaction with the resource still bound, receives the read-only flag, skipped on a rollback-only transaction, and vetoes the commit when it throws. 37 across the two managers now, all green, reactor verify passes the 80% Jacoco gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7299490d0d
commit
0194470c1f
@@ -21,7 +21,7 @@ Non parla con Hibernate o JDBC direttamente: espone solo astrazioni e un runtime
|
||||
- `RepositorySupport<T, ID>`: helper interno condiviso.
|
||||
- `TransactionPropagation`: semantica di propagazione.
|
||||
- `TransactionIsolation`: livello di isolamento.
|
||||
- `TxSynchronization`: hook lifecycle.
|
||||
- `TxSynchronization`: hook lifecycle (vedi sotto).
|
||||
|
||||
## Modello di esecuzione
|
||||
|
||||
@@ -42,6 +42,46 @@ Il flusso è:
|
||||
- `NOT_SUPPORTED`: sospende la tx corrente ed esegue senza tx.
|
||||
- `MANDATORY`: richiede una tx attiva.
|
||||
|
||||
## Synchronization (`TxSynchronization`)
|
||||
|
||||
Hook sul ciclo di vita di **una** transazione, registrati con `Data.afterCommit(...)` (o
|
||||
direttamente con `ResourceRegistry.addSynchronization(...)`).
|
||||
|
||||
Ogni callback appartiene esattamente alla transazione più interna attiva al momento della
|
||||
registrazione, e scatta una volta sola quando *quella* transazione completa:
|
||||
|
||||
- una tx interna **joined** (`REQUIRED`) non è una transazione a sé, quindi i callback registrati
|
||||
al suo interno aspettano il commit più esterno;
|
||||
- una tx `REQUIRES_NEW` lo è, quindi completarla fa scattare solo i propri callback e lascia in
|
||||
sospeso quelli della transazione esterna sospesa.
|
||||
|
||||
### Posizione rispetto al commit
|
||||
|
||||
| hook | quando | risorsa |
|
||||
| --- | --- | --- |
|
||||
| `beforeCommit(readOnly)` | subito **prima** del commit reale | sessione/connection ancora **bound**, tx ancora attiva |
|
||||
| `afterCommit()` / `afterRollback()` | dopo il completamento | risorsa già **sganciata** |
|
||||
| `afterCompletion(outcome)` | dopo i due precedenti | risorsa già sganciata |
|
||||
|
||||
`beforeCommit` è l'unico hook che può ancora scrivere sulla stessa risorsa e finire nella stessa
|
||||
unità atomica: flush di un buffer, riga di audit, valore derivato. Non viene eseguito se la
|
||||
transazione è già `rollback-only`, perché non c'è nessun commit da precedere.
|
||||
|
||||
I callback post-completamento girano invece a risorsa sganciata: uno che apre una propria
|
||||
transazione ne ottiene una **nuova** invece di agganciarsi a quella appena conclusa. È questo che
|
||||
li rende il posto giusto per invalidare una cache, accodare un messaggio o notificare qualcosa
|
||||
fuori dal database.
|
||||
|
||||
### Fallimenti
|
||||
|
||||
Lanciare da `beforeCommit` **veta il commit**: la transazione va in rollback, scattano
|
||||
`afterRollback`/`afterCompletion(ROLLED_BACK)` e l'eccezione arriva al chiamante. È il motivo per
|
||||
cui l'hook gira prima del commit e non dopo — può ancora rifiutare.
|
||||
|
||||
Gli hook post-completamento non hanno questo potere: la transazione è già chiusa quando girano,
|
||||
quindi un'eccezione si propaga ma non cambia nulla di ciò che è stato committato, e blocca i
|
||||
callback in coda dietro di lei.
|
||||
|
||||
## Uso di `Repository`
|
||||
|
||||
`Repository` è la base comune per le repository concrete.
|
||||
@@ -79,3 +119,6 @@ Questo rende il layer dati componibile con il sistema di extension di Flash senz
|
||||
- Lo stack transazionale è thread-local e viene ripulito quando torna vuoto.
|
||||
- Le risorse backend sono sospese e ripristinate per `REQUIRES_NEW` e `NOT_SUPPORTED`.
|
||||
- `TxSynchronization` è il punto di aggancio per hook di commit/rollback/completion.
|
||||
- Le synchronization sono in una lista thread-local; ogni transazione nuova registra quante ne
|
||||
esistevano già alla sua apertura e fa scattare solo la propria coda, così una `REQUIRES_NEW`
|
||||
non trascina con sé quelle della transazione sospesa.
|
||||
|
||||
Reference in New Issue
Block a user