1. Giới thiệu về Transactional
Trong quá trình phát triển ứng dụng, đặc biệt là các ứng dụng giao dịch, việc bảo đảm tính nhất quán và toàn vẹn của dữ liệu là vô cùng quan trọng. Đây là lúc khái niện “Transaction” xuất hiện. Một Transaction là một đơn vị công việc logic, trong đó tất cả các bước phải hoàn thành thành công hoặc không có bước nào được thực hiện. Điều này đảm bảo rằng hệ thống không bao giờ ở trong trạng thái không nhất quán.
Trong Spring Boot, anotation @Transactional được sử dụng để quản lý các transaction một cách dễ dàng và hiệu quả. Khi một phương thức được đánh dấu bằng @Transactional, Spring sẽ tự động quản lý việc bắt đầu và kết thúc transaction cho phương thức đó.
2. Ví dụ về việc không quản lý Transaction
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
Account fromAccount = accountRepository.findById(fromAccountId).orElseThrow(() -> new RuntimeException("Account not found"));
Account toAccount = accountRepository.findById(toAccountId).orElseThrow(() -> new RuntimeException("Account not found"));
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
toAccount.setBalance(toAccount.getBalance().add(amount));
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
}
Trong ví dụ trên, phương thức transferMoney thực hiện các bước:
- Lấy thông tin tài khoản fromAccount và toAccount
- Thực hiện trừ số tiền từ fromAccount và cộng tiền cho toAccount
- Lưu thông tin cả 2 tài khoản vào db.
Nếu mọi việc diễn ra suôn sẻ, giao dịch sẽ thành công. Tuy nhiên, nếu có một exception xảy ra sau khi lưu thành công thông tin tài khoản fromAccount nhưng trước khi lưu thông tin tài khoản toAccount, hệ thống sẽ rơi vào trạng thái không nhất quán. Tiền bị trừ khỏi tài khoản này nhưng chưa được cộng vào tài khoản kia.
3. Cách thức hoạt động của @Transactional
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
Account fromAccount = accountRepository.findById(fromAccountId).orElseThrow(() -> new RuntimeException("Account not found"));
Account toAccount = accountRepository.findById(toAccountId).orElseThrow(() -> new RuntimeException("Account not found"));
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
toAccount.setBalance(toAccount.getBalance().add(amount));
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
}
Vẫn là ví dụ chuyển tiền ở trên, nhưng lần này được đánh dấu với annotation Transactional, lúc này function transferMoney sẽ được thực thi trong một transaction, nghĩa là transaction sẽ xuyên suốt từ khi bắt đầu function tới khi kết thúc. Spring sẽ tạo một proxy object cho function và sau khi function thực thi xong, proxy sẽ commit hoặc rollback transaction nếu có lỗi xảy ra trong quá trình thực thi. Nghĩa là transaction sẽ đảm bảo được tính nhất quán của dữ liệu.
4. Các thuộc tính của @Transactional
Annotation Transactional cung cấp nhiều thuộc tính để tùy chỉnh hành vi của Transaction, chẳng hạn như propagation, isolation, timeout và rollbackfor.
- propagation: xác định cách transaction hiện tại được truyền tải từ một function gọi đến một function khác trong cùng một hoặc nhiều service class.
- REQUIRED(deafult): Nếu có một transaction đang hoạt động thì sẽ dùng chung với function được gọi, nếu không function được gọi sẽ được tạo 1 transaction mới
- SUPPORTS: Sử dụng lại một transaction đang hoạt động, nghĩa là function được gọi sẽ được nằm trong transaction đang hoạt động.
- MANDATORY: Function gọi đến function được đánh dấu propagation = MANDATORY sẽ bắt buộc phải đánh dấu Transactional
- NEVER: Function được gọi bắt buộc không nằm trong một transaction đang hoạt động
- NOT_SUPPORTED: dừng Transaction hiện tại và thực hiện method không thuộc một transaction đang hoạt động nào
- REQUIRES_NEW: khi một transaction đang hoạt động call đến method được đánh dấu transaction với propagation là REQUIRES_NEW thì transaction đang hoạt động sẽ được tạm dừng và một transaction mới được mở để thực hiện method, mọi dữ liệu thực hiện trong transaction mới sẽ được commit xuống db nếu transaction hoàn thành thành công. Sau khi transaction mới hoàn thành, transaction cũ sẽ được kích hoạt và hoạt động cho tới khi kết thúc transaction.
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
@Autowired
private AnotherService anotherService;
@Transactional
public void outerMethod() {
// Some logic
myRepository.save(new Entity());
// Calling another method with REQUIRES_NEW propagation
anotherService.innerMethod();
}
}
@Service
public class AnotherService {
@Autowired
private MyRepository myRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerMethod() {
// This method will run in a new transaction
myRepository.save(new AnotherEntity());
}
}
Trong ví dụ trên outerMethod được wrap trong 1 transaction và khi gọi method innerMethod trong anotherService thì transaction của hàm outerMethod sẽ được tạm dừng đồng thời mở một transaction mới để thực hiện hàm innerMethod, sau khi hàm innerMethod hoàn thành thì sẽ tiếp tục transation của hàm outerMethod.
- isolation: Đảm bảo tính cô lập của transaction
- timeout: thời gian tối đa mà transaction có thể chạy trước khi bị timeout.
- rollbackfor: xác định exception sẽ rollback
5. Kết luận
Sử dụng @Transactional giúp quản lý transaction một cách hiệu quả và đảm bảo được tính nhất quán của dữ liệu. Bằng cách sử dụng các thuộc tính của Transactional có thể kiểm soát được hành vi của transaction theo logic của từng function.