+5

[Redis] - Spring Boot Redis Transaction

Trong bài viết này chúng ta sẽ tìm hiểu về Redis Transaction.

Nói qua một chút về Database Transaction, nói ngắn gọn đây là một tập hợp của một trong các hành động read/insert/update/delete được thực hiện trong một đơn vị công việc, chúng sẽ thành công tất hoặc các thay đổi sẽ bị loại bỏ trong trường hợp có lỗi xảy ra. Đối với những người từng làm việc với CSDL quan hệ chắc sẽ không xa lạ với khái niệm này. Thế còn đối với CSDL phi quan hệ (NoSQL) Redis thì thế nào? Hầu hết các lệnh redis được thực thi dưới dạng get/set. Các lệnh này mặc định là nguyên tử, lệnh này thực thi không quan tâm đến lệnh khác thực thi thế nào. Nhưng khi chúng ta cần thực hiện một nhóm lệnh theo thứ tự tương tự một tập hợp các hành động như Database Transaction thì nó không được đảm bảo là nguyên tử nữa. Redis cung cấp cơ chế xử lý tương tự database transaction tthông qua các lệnh multi, execdiscard.

Đầu tiên chúng ta nói với redis rằng chúng ta sẽ thực thi một tập hợp các thao tác bằng cách gọi command multi. Sau đó chúng ta thực hiện các thao tác (A, B và C). Sau khi thực hiện xong, chúng ta sẽ gọi command execute nếu không có lỗi xảy ra hoặc gọi lệnh discard để bỏ qua các thay đổi.

Sample Application

Ví dụ chuyển tiền giữa 2 tài khoản ngân hàng sử dụng Redis Transaction.

Class Account đại diện cho tài khoản ngân hàng của người dùng.

@Data
@AllArgsConstructor(staticName = "of")
public class Account implements Serializable {

    private int userId;
    private int balance;

}

Redis Transaction – SessionCallBack

Spring Data Redis cung cấp interface SessionCallBack, chúng ta cần implement interface này khi chúng ta cần thực hiện nhiều hoạt động như một transaction. MoneyTransfer là một implement của SessionCallBack chứa logic nghiệp vụ để chuyển tiền dựa vào mã tài khoản và số tiền cần chuyển.

@AllArgsConstructor(staticName = "of")
public class MoneyTransfer implements SessionCallback<List<Object>> {

    public static final String ACCOUNT = "account";
    private final int fromAccountId;
    private final int toAccountId;
    private final int amount;

    @Override
    public <K, V> List<Object> execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
        var operations = (RedisTemplate<Object, Object>) redisOperations;
        var hashOperations = operations.opsForHash();
        var fromAccount = (Account) hashOperations.get(ACCOUNT, fromAccountId);
        var toAccount = (Account) hashOperations.get(ACCOUNT, toAccountId);
        if(Objects.nonNull(fromAccount) && Objects.nonNull(toAccount) && fromAccount.getBalance() >= amount){
            try{
                operations.multi();
                fromAccount.setBalance(fromAccount.getBalance() - amount);
                toAccount.setBalance(toAccount.getBalance() + amount);
                hashOperations.put(ACCOUNT, fromAccountId, fromAccount);
                hashOperations.put(ACCOUNT, toAccountId, toAccount);
                return operations.exec();
            }catch (Exception e){
                operations.discard();
            }
        }
        return Collections.emptyList();
    }
}

Main application để test redis transaction.

@SpringBootApplication
public class RedisTransactionApplication implements CommandLineRunner {

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

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Override
    public void run(String... args) throws Exception {

        // initialize some accounts
        this.redisTemplate.opsForHash().put(MoneyTransfer.ACCOUNT, 1, Account.of(1, 100));
        this.redisTemplate.opsForHash().put(MoneyTransfer.ACCOUNT, 2, Account.of(2, 20));

        // do the transaction
        this.redisTemplate.execute(MoneyTransfer.of(1, 2, 30));

        // print the result
        System.out.println(this.redisTemplate.opsForHash().get(MoneyTransfer.ACCOUNT, 1));
        System.out.println(this.redisTemplate.opsForHash().get(MoneyTransfer.ACCOUNT, 2));

    }
}

Kết quả:

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

Tổng kết

Trên đây là hướng dẫn để mọi người hiểu về cách Spring Redis Transaction hoạt động. Hi vọng bài viết hữu ích với mọi người.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.