Class JdbcIdempotencyRepository

java.lang.Object
io.github.ravocode.avoonce.jdbc.JdbcIdempotencyRepository
All Implemented Interfaces:
IdempotencyRepository

public class JdbcIdempotencyRepository extends Object implements IdempotencyRepository
A distributed IdempotencyRepository backed by any JDBC-compatible database.

This class has zero framework dependencies: it uses only java.sql.* from the JDK and is usable from Spring Boot, Quarkus, Micronaut, JAX-RS, or plain Java. Framework-specific wiring (e.g., Spring Boot auto-configuration) is handled by the idempotency-spring-boot-starter module.

Supported databases

  • PostgreSQL — INSERT … ON CONFLICT DO NOTHING
  • MySQL / MariaDB — INSERT IGNORE …
  • H2 — INSERT IGNORE …
  • Any other JDBC driver — plain INSERT, duplicate-key detected via SQLState 23xxx

Atomicity

The acquireOrGet(String) method uses an optimistic insert as the distributed lock primitive: the first caller to INSERT a STARTED row wins the lock; concurrent callers receive a duplicate-key signal from the database (mapped to IdempotencyConflictException).

Header serialisation

Response headers (Map<String, List<String>>) are persisted as a compact plain-text string requiring no external libraries. Format: name=v1|v2;name2=v3. Values containing ;, =, or | are percent-encoded.

Usage (non-Spring)

DataSource ds = ...; // your connection pool
new JdbcIdempotencyTableInitializer().initialize(ds);  // one-time DDL
IdempotencyRepository repo = new JdbcIdempotencyRepository(ds, new IdempotencyConfig());
IdempotencyManager manager = new IdempotencyManager(repo);
  • Constructor Details

    • JdbcIdempotencyRepository

      public JdbcIdempotencyRepository(DataSource dataSource, IdempotencyConfig config)
      Constructs a JDBC-backed repository using the supplied data source and configuration.
      Parameters:
      dataSource - the JDBC data source used to access the idempotency table
      config - the idempotency TTL and lock-timeout settings
  • Method Details

    • acquireOrGet

      public Optional<IdempotencyRecord> acquireOrGet(String idempotencyKey)
      Delegates to acquireOrGet(String, String) with a null hash.
      Specified by:
      acquireOrGet in interface IdempotencyRepository
      Parameters:
      idempotencyKey - The unique key for the request.
      Returns:
      An optional containing the existing record if it's completed, or an empty optional if a new lock was acquired.
    • acquireOrGet

      public Optional<IdempotencyRecord> acquireOrGet(String idempotencyKey, String requestHash)
      Atomically acquires a distributed lock or returns an existing COMPLETED record.

      Algorithm:

      1. Attempt INSERT of a STARTED row.
      2. If the insert succeeds (1 row affected) → lock acquired, return empty().
      3. If the insert fails (0 rows / duplicate key) → SELECT the existing row and:
        • COMPLETED: validate hash, return the record for replay.
        • STARTED, not expired: throw IdempotencyConflictException.
        • STARTED, expired OR FAILED: delete stale row and re-insert.
      Specified by:
      acquireOrGet in interface IdempotencyRepository
      Parameters:
      idempotencyKey - The unique key for the request.
      requestHash - The hash of the request payload to ensure the key is not reused for a different payload.
      Returns:
      An optional containing the existing record if it's completed, or an empty optional if a new lock was acquired.
    • saveSuccess

      public void saveSuccess(String idempotencyKey, IdempotencyResponse response)
      Description copied from interface: IdempotencyRepository
      Updates an existing record with a successful payload and sets the state to COMPLETED.
      Specified by:
      saveSuccess in interface IdempotencyRepository
      Parameters:
      idempotencyKey - The unique key for the request.
      response - The response to save.
    • saveFailure

      public void saveFailure(String idempotencyKey, String errorMessage)
      Description copied from interface: IdempotencyRepository
      Clears the active lock and updates the state to FAILED. Usually implies the request can be retried by the client.
      Specified by:
      saveFailure in interface IdempotencyRepository
      Parameters:
      idempotencyKey - The unique key for the request.
      errorMessage - The error message to save.
    • get

      public Optional<IdempotencyRecord> get(String idempotencyKey)
      Description copied from interface: IdempotencyRepository
      Pure read-only check of the current record.
      Specified by:
      get in interface IdempotencyRepository
      Parameters:
      idempotencyKey - The unique key for the request.
      Returns:
      An optional containing the record if found, empty otherwise.
    • delete

      public void delete(String idempotencyKey)
      Description copied from interface: IdempotencyRepository
      Removes the idempotency record associated with the given key. Useful for administrative cleanup or explicit invalidation.
      Specified by:
      delete in interface IdempotencyRepository
      Parameters:
      idempotencyKey - The unique key to remove.
    • evictExpired

      public int evictExpired()
      Description copied from interface: IdempotencyRepository
      Evicts all expired records from the underlying storage. Useful for periodic cleanup in persistent data stores.
      Specified by:
      evictExpired in interface IdempotencyRepository
      Returns:
      The number of records deleted.