Duplicate writes observed when retrying JPA persist with IDENTITY after rollback
0 reputation · 21 Apr 2021, 12:30 UTC
0 reputation · 21 Apr 2021, 12:30 UTC
When a Spring‑annotated method uses @Transactional and @Retryable to persist a JPA entity whose primary key is generated by the database (IDENTITY), a transient exception thrown after the INSERT causes the transaction to roll back. On retry the same persist call is executed again, but the entity instance still holds the identifier that was assigned during the first attempt. Because the identifier is not regenerated, the second INSERT attempts to reuse the same key, which can result in a duplicate row if the database does not reject the duplicate key.
The goal is to configure the retry mechanism so that a retried write is safe without requiring the developer to manually add idempotency checks. The uncertainty lies in whether the retry framework should automatically invoke a user‑provided idempotency validator or leave that responsibility to the application code, leaving the behavior ambiguous in the current documentation.
Should Spring Retry provide an optional idempotency‑check callback that runs before each retry attempt? Does Resilience4j’s Retry module expose a hook for verifying idempotency when used with manual transaction boundaries? What configuration or pattern guarantees that a retry after a rollback will not produce duplicate writes for IDENTITY‑generated keys?
35275 reputation · 21 Apr 2021, 17:03 UTC
Spring Retry does not provide an optional idempotency-check callback that runs before each retry attempt, and Resilience4j Retry does not expose a built-in idempotency validation hook for manual transaction boundaries. Both are generic retry mechanisms. Ensuring a retried write is safe with JPA IDENTITY is application code responsibility, not framework configuration.
The retry reuses the same entity instance. The first attempt flushes, the database assigns an id, then a transient exception occurs after the insert but before commit. The transaction rolls back, but the entity still holds the assigned id. On retry the same instance is persisted again, so JPA sees a non-null id and attempts to re-use it, leading to duplicate-key errors or unintended updates.
Using SEQUENCE or TABLE generation makes safe retry easier because the id can be allocated before the insert, but with IDENTITY the only reliable approach is a fresh entity and fresh persistence context per attempt.
Confirm whether the transient exception occurs after the INSERT has been flushed and the id assigned but before commit. If the failure happens before flush, the id is still null and the current symptom would not occur. Log entity.getId() before persist and after rollback to verify id retention.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.