MrJazsohanisharma

Redis Transactions in Spring Boot

Using Redis Transactions in Spring Boot: Ensuring Atomic Operations

In distributed systems and high-performance applications, atomicity is crucial for data consistency—especially when multiple operations must succeed or fail as a single unit. Redis supports transactions, allowing you to execute a group of commands atomically. In this guide, you'll learn how to use Redis transactions in Spring Boot to ensure atomic operations, complete with a practical example.

What Are Redis Transactions?

A Redis transaction is a sequence of commands executed as a single, isolated operation. All commands in the transaction are queued and either executed together (EXEC) or discarded (DISCARD). This guarantees atomicity: either all operations succeed, or none do. Redis transactions use the commands MULTI, EXEC, DISCARD, and optionally WATCH for optimistic locking.

How Redis Transactions Work in Spring Boot

Spring Data Redis provides the SessionCallback interface to group multiple Redis operations into a single transaction. This ensures all commands are executed on the same connection and as a single atomic unit.

Practical Example: Atomic Money Transfer

Suppose you have a simple bank application where you need to transfer money between two accounts. You want to ensure that both the debit and credit operations happen together, or not at all.

1. Define the Account Entity

@Data
@AllArgsConstructor(staticName = "of")
public class Account implements Serializable {
    private int userId;
    private int balance;
}

2. Implement the Transaction Logic with SessionCallback

public class MoneyTransfer implements SessionCallback<Object> {

    public static final String ACCOUNT = "account";

    private final int fromId;
    private final int toId;
    private final int amount;

    private MoneyTransfer(int fromId, int toId, int amount) {
        this.fromId = fromId;
        this.toId = toId;
        this.amount = amount;
    }

    public static MoneyTransfer of(int fromId, int toId, int amount) {
        return new MoneyTransfer(fromId, toId, amount);
    }

    @Override
    public Object execute(RedisOperations operations) {
        operations.multi();
        Account from = (Account) operations.opsForHash().get(ACCOUNT, fromId);
        Account to = (Account) operations.opsForHash().get(ACCOUNT, toId);

        if (from.getBalance() < amount) {
            operations.discard();
            throw new IllegalArgumentException("Insufficient balance");
        }

        from.setBalance(from.getBalance() - amount);
        to.setBalance(to.getBalance() + amount);

        operations.opsForHash().put(ACCOUNT, fromId, from);
        operations.opsForHash().put(ACCOUNT, toId, to);

        return operations.exec();
    }
}

3. Execute the Transaction in Your Application

@SpringBootApplication
public class RedisTransactionApplication implements CommandLineRunner {

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    public static void main(String[] args) {
        SpringApplication.run(RedisTransactionApplication.class, args);
    }

    @Override
    public void run(String... args) {
        // Initialize accounts
        redisTemplate.opsForHash().put(MoneyTransfer.ACCOUNT, 1, Account.of(1, 100));
        redisTemplate.opsForHash().put(MoneyTransfer.ACCOUNT, 2, Account.of(2, 20));

        // Perform atomic money transfer
        redisTemplate.execute(MoneyTransfer.of(1, 2, 30));

        // Print updated accounts
        System.out.println(redisTemplate.opsForHash().get(MoneyTransfer.ACCOUNT, 1));
        System.out.println(redisTemplate.opsForHash().get(MoneyTransfer.ACCOUNT, 2));
    }
}

Output:

Account(userId=1, balance=70)
Account(userId=2, balance=50)

Best Practices and Notes

  • Atomicity: All queued commands are executed together. If any error occurs before EXEC, nothing is applied.
  • SessionCallback: Always use SessionCallback for multi-command transactions to ensure operations use the same connection.
  • Enable Transaction Support: For @Transactional support, set setEnableTransactionSupport(true) on your RedisTemplate.
  • Error Handling: Use operations.discard() to abort a transaction if a business rule fails.

Conclusion

Redis transactions in Spring Boot are straightforward and powerful for ensuring atomic multi-step operations. By leveraging SessionCallback and Spring Data Redis, you can group related commands together, guaranteeing that your critical business logic executes safely and consistently.

Redis transactions: simple, fast, and essential for atomic operations in distributed systems.

Previous Post Next Post

Blog ads

ads