or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

channel-message-store.mdinbound-polling.mdindex.mdjava-dsl.mdlock-registry.mdmessage-store.mdmetadata-store.mdoutbound-gateway.mdoutbound-updates.mdparameter-factories.mdpostgres-channels.mdstored-procedures.md
tile.json

lock-registry.mddocs/

Lock Registry

The JDBC Lock Registry provides distributed locking capabilities using database storage for coordination across multiple application instances. This enables synchronized access to shared resources, leader election, and preventing duplicate processing in clustered environments.

Key Information for Agents

Required Dependencies:

  • spring-integration-jdbc (this package)
  • spring-integration-core is required
  • DataSource bean must be configured
  • Database schema must exist (single table)

Default Behaviors:

  • Default TTL: Duration.ofSeconds(10)
  • Default table prefix: "INT_" (table: INT_LOCK)
  • Default region: "DEFAULT"
  • Default idleBetweenTries: Duration.ofMillis(100)
  • Default cacheCapacity: 100,000 locks
  • checkDatabaseOnStart=true (validates schema on startup)
  • Locks automatically expire after TTL
  • Lock renewal extends expiration time

Threading Model:

  • Thread-safe when using JdbcOperations (connection pooling)
  • Lock acquisition involves database round-trip
  • Local lock cache reduces database calls for same lock key
  • Not suitable for high-frequency locking (database overhead)

Lifecycle:

  • DefaultLockRepository implements SmartLifecycle (auto-starts by default)
  • Checks database schema on startup (can be disabled)
  • Stops gracefully on context shutdown

Exceptions:

  • DataAccessException - Database access failures
  • IllegalArgumentException - Invalid configuration
  • LockAcquisitionException - Lock acquisition failures (timeout, etc.)

Edge Cases:

  • Lock TTL expiration requires clock synchronization across instances
  • Lock renewal extends expiration (use for long-running operations)
  • expireUnusedOlderThan() removes stale locks from cache and database
  • Client ID identifies JVM instance (should be unique per instance)
  • Region partitioning allows multiple logical lock spaces to share same physical table
  • Lock fairness not guaranteed (depends on database transaction isolation)
  • Deadlock prevention requires consistent lock ordering
  • High availability: Database must be highly available as single point of coordination

Core Classes

JdbcLockRegistry

package org.springframework.integration.jdbc.lock;

public class JdbcLockRegistry
    implements ExpirableLockRegistry<DistributedLock>, RenewableLockRegistry<DistributedLock> {

    public static final Duration DEFAULT_TTL = Duration.ofSeconds(10);

    public JdbcLockRegistry(LockRepository client);
    public JdbcLockRegistry(LockRepository client, Duration expireAfter);

    public void setIdleBetweenTries(Duration idleBetweenTries);
    public void setCacheCapacity(int cacheCapacity);

    public DistributedLock obtain(Object lockKey);
    public void expireUnusedOlderThan(long age);
    public void renewLock(Object lockKey);
    public void renewLock(Object lockKey, Duration customTtl);
}

DefaultLockRepository

package org.springframework.integration.jdbc.lock;

public class DefaultLockRepository implements LockRepository, InitializingBean,
        ApplicationContextAware, SmartInitializingSingleton, SmartLifecycle {

    public static final String DEFAULT_TABLE_PREFIX = "INT_";
    public static final Duration DEFAULT_TTL = Duration.ofSeconds(10);

    public DefaultLockRepository(DataSource dataSource);
    public DefaultLockRepository(DataSource dataSource, String id);

    public void setRegion(String region);
    public void setPrefix(String prefix);
    public void setTransactionManager(PlatformTransactionManager transactionManager);
    public void setUpdateQuery(String updateQuery);
    public void setInsertQuery(String insertQuery);
    public void setRenewQuery(String renewQuery);
    public void setCheckDatabaseOnStart(boolean checkDatabaseOnStart);

    public boolean acquire(String lock, Duration ttl);
    public boolean renew(String lock, Duration ttl);
    public boolean isAcquired(String lock);
    public boolean delete(String lock);
    public void deleteExpired();
}

Usage Examples

Basic Lock Usage

import org.springframework.integration.jdbc.lock.JdbcLockRegistry;
import org.springframework.integration.jdbc.lock.DefaultLockRepository;
import java.util.concurrent.locks.Lock;

// Basic lock registry setup
DefaultLockRepository lockRepository = new DefaultLockRepository(dataSource);
JdbcLockRegistry lockRegistry = new JdbcLockRegistry(lockRepository);

// Obtain and use lock
Lock lock = lockRegistry.obtain("resource-1");
try {
    lock.lock();
    // Critical section - only one JVM can execute this at a time
    System.out.println("Processing shared resource");
    Thread.sleep(1000);
} finally {
    lock.unlock();
}

Lock with Timeout

// Lock with timeout
JdbcLockRegistry timeoutRegistry = new JdbcLockRegistry(
    new DefaultLockRepository(dataSource)
);

Lock timeoutLock = timeoutRegistry.obtain("shared-resource");
if (timeoutLock.tryLock(5, TimeUnit.SECONDS)) {
    try {
        // Got lock within 5 seconds
        performOperation();
    } finally {
        timeoutLock.unlock();
    }
} else {
    // Timeout - couldn't acquire lock
    System.out.println("Failed to acquire lock within timeout");
}

Renewable Locks

// Renewable locks for long-running operations
JdbcLockRegistry renewableRegistry = new JdbcLockRegistry(
    new DefaultLockRepository(dataSource),
    Duration.ofSeconds(10)
);

Lock renewableLock = renewableRegistry.obtain("batch-job");
renewableLock.lock();
try {
    for (int i = 0; i < 100; i++) {
        // Process batch item
        processBatchItem(i);

        // Renew lock every 10 items to prevent expiration
        if (i % 10 == 0) {
            renewableRegistry.renewLock("batch-job");
        }
    }
} finally {
    renewableLock.unlock();
}

Lock Cleanup

// Expire old unused locks (maintenance task)
JdbcLockRegistry maintenanceRegistry = new JdbcLockRegistry(
    new DefaultLockRepository(dataSource)
);

// Schedule periodic cleanup
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
    // Expire locks unused for more than 1 hour
    maintenanceRegistry.expireUnusedOlderThan(3600000);
}, 0, 1, TimeUnit.HOURS);

Integration with Spring Integration

JdbcLockRegistry integrates with leader election and locked behavior patterns:

import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.leader.LockRegistryLeaderInitiator;

@Bean
public DefaultLockRepository lockRepository(DataSource dataSource) {
    DefaultLockRepository repository = new DefaultLockRepository(dataSource);
    repository.setRegion("PRODUCTION");
    return repository;
}

@Bean
public JdbcLockRegistry lockRegistry(DefaultLockRepository lockRepository) {
    JdbcLockRegistry registry = new JdbcLockRegistry(lockRepository, Duration.ofSeconds(30));
    registry.setIdleBetweenTries(Duration.ofMillis(100));
    return registry;
}

@Bean
public LockRegistryLeaderInitiator leaderInitiator(JdbcLockRegistry lockRegistry) {
    return new LockRegistryLeaderInitiator(lockRegistry, "cluster-leader");
}

Database Schema

Required table (with default prefix "INT_"):

CREATE TABLE INT_LOCK (
    LOCK_KEY CHAR(36) NOT NULL,
    REGION VARCHAR(100) NOT NULL,
    CLIENT_ID CHAR(36),
    CREATED_DATE TIMESTAMP NOT NULL,
    CONSTRAINT INT_LOCK_PK PRIMARY KEY (LOCK_KEY, REGION)
);

SQL scripts are provided in org/springframework/integration/jdbc/schema-*.sql for various databases.

Key Considerations

  • Distributed Coordination: Locks coordinate across multiple JVM instances using database
  • TTL Management: Locks automatically expire after TTL; renewal extends expiration
  • Transaction Safety: Use transaction manager for atomic lock operations
  • Performance: Lock acquisition involves database round-trip; not suitable for high-frequency locking
  • Lock Leasing: Locks use time-based leasing rather than connection-based locking
  • Client ID: Each JVM instance should have unique client ID for tracking
  • Region Partitioning: Multiple logical lock spaces can share same physical table
  • Cleanup: Implement periodic cleanup of expired locks via expireUnusedOlderThan()
  • Cache Capacity: Set appropriate cache size based on number of distinct lock keys
  • Retry Interval: Tune idleBetweenTries based on contention patterns
  • Lock Fairness: No guaranteed fairness; depends on database transaction isolation
  • Deadlock Prevention: Use consistent lock ordering to avoid deadlocks
  • Monitoring: Track lock acquisition failures and lock hold times
  • High Availability: Database must be highly available as single point of coordination
  • Clock Synchronization: Server clocks should be synchronized for accurate TTL behavior