Transaction management
Spring Boot দিয়ে কাজ করার সময় আমরা অনেক সময় database transaction নিয়ে কাজ করি — যেমন একই সময়ে একাধিক টেবিলে ডেটা ইনসার্ট, আপডেট বা ডিলিট করতে হয়। কিন্তু মাঝপথে কোনো ত্রুটি ঘটলে, কিভাবে সব পরিবর্তন rollback হবে? এখানেই আসে Transaction Management।
Transaction কী?
একটি Transaction হলো ডাটাবেজের একটি অপারেশনের ইউনিট যা সবগুলো ধাপ সফলভাবে সম্পন্ন হলে তবেই commit হয়, অন্যথায় rollback হয়।
এর মানে হলো — আংশিকভাবে কোনো পরিবর্তন ডাটাবেজে যাবে না।
উদাহরণ:
1️⃣ ব্যাংক A থেকে 100 টাকা কমাও
2️⃣ ব্যাংক B তে 100 টাকা বাড়াও
যদি প্রথম ধাপ সফল হয়, কিন্তু দ্বিতীয় ধাপে ত্রুটি ঘটে — তাহলে পুরো Transaction rollback হবে, এবং কোনো পরিবর্তনই থাকবে না।
Spring Boot এ Transaction Management কিভাবে কাজ করে?
Spring Boot এ Transaction পরিচালনা করতে আমরা সাধারণত @Transactional annotation ব্যবহার করি। এটি Spring Framework কে বলে দেয় যে এই মেথডের সব ডাটাবেজ অপারেশন একটি Transaction এর মধ্যে চলবে।
Service Layer এ Transaction ব্যবহার
@Service
public class TransactionService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transferMoney(Long fromId, Long toId, double amount) {
Account fromAccount = accountRepository.findById(fromId)
.orElseThrow(() -> new RuntimeException("From account not found"));
Account toAccount = accountRepository.findById(toId)
.orElseThrow(() -> new RuntimeException("To account not found"));
fromAccount.setBalance(fromAccount.getBalance() - amount);
toAccount.setBalance(toAccount.getBalance() + amount);
accountRepository.save(fromAccount);
// উদাহরণস্বরূপ ইচ্ছাকৃতভাবে একটি exception ছুঁড়ে দেওয়া হলো
if (amount > 5000) {
throw new RuntimeException("Transfer limit exceeded!");
}
accountRepository.save(toAccount);
}
}
এখানে @Transactional নিশ্চিত করে যে যদি কোনো exception ঘটে, তাহলে পুরো ট্রান্সফার rollback হবে।
Rollback কিভাবে কাজ করে?
Spring Boot স্বয়ংক্রিয়ভাবে RuntimeException ঘটলে Transaction rollback করে দেয়।
তবে তুমি চাইলে নির্দিষ্ট Exception এর জন্য rollback নির্ধারণ করতে পারো:
@Transactional(rollbackFor = Exception.class)
Transaction Propagation
Spring Transaction propagation নির্ধারণ করে কোন transaction context ব্যবহার হবে, যখন একটি transactional method অন্য আরেকটি transactional method কে কল করে।
| Mode | ব্যাখ্যা |
|---|---|
REQUIRED (default) |
আগের transaction থাকলে সেটাই ব্যবহার হবে, না থাকলে নতুন তৈরি করবে। |
REQUIRES_NEW |
আগের transaction suspend হবে, নতুন transaction শুরু হবে। |
NESTED |
একটি transaction এর মধ্যে nested transaction তৈরি করে। rollback হলে parent safe থাকে। |
SUPPORTS |
transaction থাকলে join করে, না থাকলে non-transactional ভাবে চলে। |
MANDATORY |
transaction না থাকলে exception ছুঁড়ে দেয়। |
NEVER |
transaction থাকলে exception ছুঁড়ে দেয়। |
NOT_SUPPORTED |
সবসময় non-transactional মোডে কাজ করে। |
Example:
এখানে মূল transaction suspend হবে, এবং লগ রেকর্ড সবসময় আলাদা transaction এ save হবে।
Transaction Isolation Levels
একাধিক concurrent transaction চললে ডাটার consistency রক্ষা করা জরুরি। এজন্য ব্যবহৃত হয় isolation level।
| Level | Prevents | পারফরম্যান্স প্রভাব |
|---|---|---|
READ_UNCOMMITTED | কোনোটি না | দ্রুত কিন্তু unsafe |
READ_COMMITTED | Dirty Read | safe এবং সাধারণত default |
REPEATABLE_READ | Dirty, Non-Repeatable Read | consistent কিন্তু ধীর |
SERIALIZABLE | সব রিড সমস্যা প্রতিরোধ | সবচেয়ে নিরাপদ, কিন্তু সবচেয়ে ধীর |
Example:
Rollback Rules কাস্টমাইজ করা
Spring Boot ডিফল্টভাবে শুধু RuntimeException ঘটলে rollback করে।
তবে তুমি চাইলে নির্দিষ্ট Exception এর জন্যও rollback enforce করতে পারো:
অন্যদিকে, কোনো Exception এ rollback না চাইলে:
Read-only Transaction
যদি তোমার মেথড কেবল read/query করে, কোনো পরিবর্তন না আনে, তাহলে readOnly = true ব্যবহার করো।
এতে পারফরম্যান্স বাড়ে এবং Hibernate কিছু অপ্টিমাইজেশন প্রয়োগ করে।
Nested Transactions
Propagation.NESTED ব্যবহার করলে parent transaction এর ভিতরে savepoint তৈরি হয়।
যদি child transaction ব্যর্থ হয়, parent rollback হবে না — savepoint পর্যন্ত rollback হবে।
নোট: Nested transaction সাপোর্ট পেতে datasource কে DataSourceTransactionManager হতে হবে; JPA এর JpaTransactionManager পুরোপুরি nested support দেয় না।
Transaction Management Best Practices
- Service Layer এ @Transactional ব্যবহার করো, Controller এ নয়।
- @Transactional method যেন public হয় (Spring proxy private method handle করে না)।
- Lazy-loading সমস্যা এড়াতে Entity fetch যতটা সম্ভব service layer এর মধ্যেই সম্পন্ন করো।
- Read-only queries এর জন্য readOnly = true ব্যবহার করো।
- Exception handling করলে, RuntimeException পুনরায় throw করতে ভুলো না — না হলে rollback হবে না।
Practical Example
এখানে OrderService.placeOrder() ব্যর্থ হলে পুরো order rollback হবে,
কিন্তু PaymentService এর REQUIRES_NEW propagation থাকার কারণে তার transaction unaffected থাকবে।
Common Pitfalls
Self Invocation Issue
যদি একই ক্লাসের ভেতর একটি @Transactional method অন্যটি কল করে,
Spring proxy সেটিকে intercept করতে পারে না, ফলে transaction কাজ করবে না।
সমাধান: transaction logic আলাদা service ক্লাসে রাখো।
Checked Exception rollback না হওয়া
Spring শুধুমাত্র RuntimeException এ rollback করে।
সমাধান: rollbackFor = Exception.class যুক্ত করো।