Files
spring-boot-demo/resilience/docs/05-retry-and-transactions.md
asmhatreandClaude Opus 5 7e1676c763 Add resilience: Spring Framework 7 @Retryable and @ConcurrencyLimit
Companion code for "Spring Framework 7's Built-in Resilience: @Retryable,
@ConcurrencyLimit, and What's Left for Resilience4j". Every retry counted
by recording real invocations: defaults, backoff and jitter, timeout,
reactive and CompletableFuture returns, the concurrency limit's BLOCK and
REJECT policies, retries around transactions, composition with
Resilience4j 2.4.0, and the annotation API across 7.0.0-7.0.9.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01C3TETMrqVUWeFkNtz3Jbo3
2026-09-11 17:12:25 +00:00

52 lines
2.5 KiB
Markdown

# 5. Retry and transactions
[← 4. @ConcurrencyLimit](04-concurrency-limit.md) · [Index](../README.md) · Next: [6. What is left for Resilience4j →](06-what-is-left-for-resilience4j.md)
[`StockWriter.record`](../src/main/java/com/ankurm/resilience/tx/StockWriter.java) is
`@Retryable @Transactional`, inserts a row, and fails twice.
## Which interceptor is outside
`RetryAnnotationBeanPostProcessor`'s constructor calls `setBeforeExistingAdvisors(true)` (`javap -c`
on spring-context 7.0.9). The transaction advisor is applied first by the auto-proxy creator; the
retry advisor is then inserted at index 0 ([`proxy-stock-writer.txt`](output/proxy-stock-writer.txt)).
The `order` attribute of `@EnableResilientMethods` is the post-processor's order, not the
advice's position, and does not change this.
## Alone: each attempt is its own transaction
[`tx-standalone.txt`](output/tx-standalone.txt) - three connection holders, one row:
```
"attempts": [
"attempt 1: transaction active=true, connection holder @6aa73704",
"attempt 2: transaction active=true, connection holder @1978b580",
"attempt 3: transaction active=true, connection holder @7d5534c2"
],
"rows in stock_movement": 1
```
This is what you want: the failed attempts rolled back their inserts.
## Called from inside a transaction: the retry cannot help
[`OrderFacade.placeOrder`](../src/main/java/com/ankurm/resilience/tx/OrderFacade.java) is
`@Transactional` and calls the same method ([`tx-joined.txt`](output/tx-joined.txt)):
```
"caller got": "org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only",
"attempts": [
"attempt 1: transaction active=true, connection holder @6135f416",
"attempt 2: transaction active=true, connection holder @6135f416",
"attempt 3: transaction active=true, connection holder @6135f416"
],
"rows in stock_movement": 0
```
Every attempt joins the outer transaction (`REQUIRED`). The first failure passes through the inner
`TransactionInterceptor`, which marks the *shared* transaction rollback-only. The third attempt
succeeds, the retry returns normally, and the outer commit throws. Three attempts, zero rows, an
exception the caller did not expect. The retry belongs at the outermost transactional boundary -
or the inner method needs `REQUIRES_NEW`. The [@Transactional article](https://ankurm.com/transactional-propagation-isolation-silent-failures/)
covers the rollback-only mechanics.