Backend Development 6 min read

Ensuring Idempotency with Distributed Locks: Adjusting Aspect Order in Java Backend

This article explains how using a custom @DistributeLock annotation together with @Transactional can cause idempotency issues due to aspect execution order, and demonstrates how to prioritize the lock aspect using @Order to ensure the lock is applied before the transaction, with code examples and best‑practice recommendations.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Ensuring Idempotency with Distributed Locks: Adjusting Aspect Order in Java Backend

In many Java projects, developers add distributed locks to avoid concurrency problems and implement an "one‑lock, two‑check, three‑update" pattern to achieve idempotent logic.

We created a custom @DistributeLock annotation that is applied to methods alongside @Transactional . By default, the lock aspect runs after the transaction aspect, so the transaction is started first, then the lock is acquired, and finally the lock is released before the transaction commits.

When another thread invokes the same method between the lock release and transaction commit, the lock is already free while the transaction is still pending. This leads to three problems:

Lock: The new thread can acquire the lock because it has been released.

Check: The database query may not see the uncommitted data from the first transaction.

Update: The second thread may perform an update that causes duplicate data or errors.

To fix this, the lock’s aspect must execute before the transaction’s aspect, i.e., the lock’s scope should be larger than the transaction’s scope.

The solution is to use the @Order annotation on the lock aspect class and set its order value to the smallest (highest priority) number, ensuring the lock is acquired before the transaction starts.

@Transactional(rollbackFor = Exception.class)
public boolean register(Request request) {
    RLock lock = redisson.getLock(request.getIdentifier());
    try {
        // 1. lock
        lock.lock();
        // 2. check
        User user = userMapper.find(request.getIdentifier());
        if (user != null) {
            return false;
        }
        // 3. update, save order data
        userMapper.insertOrder(request);
    } finally {
        lock.unlock();
    }
    return true;
}

The execution sequence becomes:

Enter transaction

Acquire lock

Release lock

Commit transaction

By adding @Order with the highest priority to the distributed‑lock aspect, the lock is always obtained before the transaction begins, eliminating the idempotency breach.

Summary

Avoid placing @Transactional on methods that also contain distributed‑lock logic when the database isolation level is REPEATABLE READ.

Prefer to keep the synchronized code block as small as possible.

Use @Order to control aspect execution order, ensuring the lock aspect runs first.

backendJavaTransactionDistributed LockIdempotencyaspect order
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.