Spring Boot වල Transaction Management: @Transactional පාවිච්චි කරලා Data ටික සුපිරියට Manage කරමු! | SC Guide

කොහොමද කට්ටිය? අද අපි කතා කරන්න යන්නේ Software Engineering වලදී ගොඩක් වැදගත් වෙන, ඒ වගේම පොඩ්ඩක් සංකීර්ණ වෙන්න පුළුවන් මාතෘකාවක් ගැන – ඒ තමයි Transaction Management, විශේෂයෙන්ම Spring Boot Framework එකත් එක්ක. මේක දන්නේ නැතුව Software එකක් හදනවා කියන්නේ, වැඩක් කරන්නේ නැතුව ඩුබායි යනවා වගේ වැඩක්. ඒ නිසා අද හොඳටම ඉගෙන ගමු මේක.
හිතන්නකෝ ඔයා Bank එකක System එකක් හදනවා කියලා. කෙනෙක්ගේ Account එකකින් තව කෙනෙක්ගේ Account එකකට සල්ලි Transfer කරනවා. මේ Transfer එකේදී පියවර දෙකක් තියෙනවා නේද? පළවෙනියට, මුල් Account එකෙන් සල්ලි අඩු වෙනවා. දෙවනුව, දෙවැනි Account එකට සල්ලි එකතු වෙනවා. දැන් හිතන්න, පළවෙනි පියවර සාර්ථකව ඉවර වෙලා, සල්ලි අඩු වුණාට පස්සේ, දෙවැනි පියවරේදී System එක Crash වුණොත් එහෙම මොකද වෙන්නේ? මුල් Account එකෙන් සල්ලි අඩු වෙලා, දෙවැනි එකට එකතු වෙලත් නැහැ. System එකට අනුව සල්ලි ටික අතුරුදහන් වෙලා! මේක නම් පට්ට අවුලක් නේද? Data Consistency නැහැ.
ඒ වගේම Online Store එකක Order එකක් දානවා කියලා හිතමු. කෙනෙක් Order එකක් දානකොට, Order Details ටික Database එකට යනවා, ඊට පස්සේ Product Inventory එකේ ඒ Product එකේ Quantity එක අඩු වෙනවා, ඊට පස්සේ Payment Process එක වෙනවා. මේ හැම පියවරක්ම සාර්ථකව වෙන්න ඕනේ. එකක් හරි Fail වුණොත්, මුළු Order එකම අහෝසි වෙලා, Database එක කලින් තිබ්බ තත්වෙටම එන්න ඕනේ. නැත්නම් Order එක අඩක් ඉවර වෙලා, Product Quantity එක වැරදිලා, Payment එක Fail වෙලා වගේ අවුල් ගොඩක් වෙන්න පුළුවන්. මේ වගේ අවුල් නැති කරගන්න අපිට Transaction Management පාවිච්චි කරන්න වෙනවා. Spring Boot වලදී මේක කරන්න පුළුවන් සුපිරිම විදිය තමයි @Transactional
Annotation එක.
Transactions කියන්නේ මොනවාද?
සරලව කිව්වොත්, Transaction එකක් කියන්නේ Database එකේ තියෙන Operation ගොඩක් එක Single, Logical Unit of Work එකක් විදියට සලකන එකට. මේ Operation ඔක්කොම එකට සාර්ථක වෙන්න ඕනේ, නැත්නම් එකක් හරි Fail වුණොත්, මුළු Transaction එකම Rollback වෙලා (අවලංගු වෙලා), Database එක කලින් තිබ්බ තත්වෙටම එන්න ඕනේ. හරියට 'All or Nothing' කියන Rule එක වගේ.
Transactions වලට ගොඩක්ම වැදගත් වෙන Concept එකක් තමයි ACID Properties. මේවා තමයි ඕනෑම Transaction එකක් අනිවාර්යයෙන්ම තෘප්ත කරන්න ඕනේ ගුණාංග:
- Atomicity (පරමාණුකත්වය): මේක තමයි 'All or Nothing' කියන එක. Transaction එකක තියෙන හැම Operation එකක්ම සාර්ථකව ඉවර වෙන්න ඕනේ. එක Operation එකක් හරි Fail වුණොත්, මුළු Transaction එකම Fail වෙලා, Database එක කලින් තිබ්බ තත්වෙටම Rollback වෙනවා. අර සල්ලි Transfer කරන කතාවේදී වගේ.
- Consistency (ස්ථාවරත්වය): Transaction එකක් ඉවර වුණාට පස්සේ Database එක Valid State එකක තියෙන්න ඕනේ. ඒ කියන්නේ Database Rules (Constraints, Triggers) හැම එකක්ම තෘප්ත වෙන්න ඕනේ.
- Isolation (හුදකලා බව): එක Transaction එකක් තවත් Transaction එකකට බලපෑම් නොකර, වෙන වෙනම ක්රියාත්මක වෙන්න ඕනේ. Transaction දෙකක් එකම වෙලාවේ ක්රියාත්මක වෙනකොට, එකකින් කරන වෙනස්කම් තව එකකට පේන්න නොදී ආරක්ෂා කරගන්න එක තමයි මේකෙන් කරන්නේ.
- Durability (කල්පැවැත්ම): Transaction එකක් සාර්ථකව Commit වුණාට පස්සේ, ඒ වෙනස්කම් Database එකේ ස්ථිරවම Save වෙන්න ඕනේ. System එක Crash වුණත්, Power Cut එකක් ආවත්, ඒ වෙනස්කම් නැති වෙන්නේ නැහැ.
මේ ACID Properties හතර තමයි Strong Transaction Management එකක පදනම.
Spring Boot වල Transactions බිල්ඩ් කරමු
Spring Boot වලදී Transaction Management කියන එක ගොඩක්ම ලේසි කරනවා. අපිට කරන්න තියෙන්නේ Service Layer එකේ Methods වලට @Transactional
Annotation එක දාන එක විතරයි. එච්චරයි! Spring Framework එකෙන් ඉතුරු ටික බලාගන්නවා. මේක Spring AOP (Aspect-Oriented Programming) Concept එක පාවිච්චි කරලා තමයි කරන්නේ.
@Transactional
Annotation එක දැම්මම මොකද වෙන්නේ? Spring Framework එකෙන් ඒ Method එක වටේට Proxy එකක් හදනවා. ඒ Proxy එකෙන් තමයි Transaction එක Start කරන එක, Commit කරන එක, නැත්නම් Rollback කරන එක වගේ දේවල් කරන්නේ. Method එක සාර්ථකව Run වෙලා Exception එකක් ආවේ නැත්නම් Transaction එක Commit වෙනවා. Unchecked Exception (Runtime Exception) එකක් ආවොත්, නැත්නම් Error එකක් ආවොත්, Transaction එක Rollback වෙනවා. Checked Exception එකක් ආවොත් නම් Rollback වෙන්නේ නැහැ, ඒ ගැන පස්සේ කියන්නම්.
උදාහරණයක් විදියට මේ වගේ Code එකක් හිතන්නකෝ:
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, double amount) {
// 1. Debit from 'fromAccountId'
Account fromAccount = accountRepository.findById(fromAccountId)
.orElseThrow(() -> new RuntimeException("From account not found"));
if (fromAccount.getBalance() < amount) {
throw new RuntimeException("Insufficient balance");
}
fromAccount.setBalance(fromAccount.getBalance() - amount);
accountRepository.save(fromAccount);
// 2. Simulate an error for demonstration
// if (true) {
// throw new RuntimeException("Simulated error after debit");
// }
// 3. Credit to 'toAccountId'
Account toAccount = accountRepository.findById(toAccountId)
.orElseThrow(() -> new RuntimeException("To account not found"));
toAccount.setBalance(toAccount.getBalance() + amount);
accountRepository.save(toAccount);
System.out.println("Money transferred successfully!");
}
}
මේ උදාහරණයේ transferMoney
Method එකට @Transactional
දාලා තියෙන නිසා, මේ Method එක ඇතුළේ වෙන Database Operations දෙකම (Debit, Credit) එක Transaction එකක් විදියට ක්රියාත්මක වෙනවා. 'Simulated error' Line එක Uncomment කරලා Run කළොත්, මුලින්ම සල්ලි අඩු වුණත්, පස්සේ ඒ Error එක ආපු ගමන් මුළු Transaction එකම Rollback වෙලා, Debit එකත් Cancel වෙනවා. එතකොට Account දෙකේම Balance එක කලින් තිබ්බ තත්වෙටම එනවා. වැඩේ පට්ට නේද?
@Transactional
වල ගතිගුණ (Properties of @Transactional
)
@Transactional
Annotation එකට තව ගොඩක් Properties තියෙනවා, ඒ Properties පාවිච්චි කරලා අපිට Transaction එකේ Behavior එක customize කරන්න පුළුවන්. මේවා තමයි ගොඩක්ම වැදගත්:
1. propagation
මේකෙන් තීරණය කරන්නේ Method එකක් Transaction එකක් ඇතුළේ කොහොමද Run වෙන්නේ කියන එක. ගොඩක්ම පාවිච්චි වෙන ඒවා තමයි:
Propagation.REQUIRED
(Default): මේක තමයි Default Behavior එක. Method එකක්@Transactional(propagation = Propagation.REQUIRED)
කියලා දැම්මොත්, ඒ Method එක Run වෙන්නේ දැනට තියෙන Transaction එකක් ඇතුළේ. දැනට Transaction එකක් නැත්නම්, අලුත් Transaction එකක් හදලා ඒක ඇතුළේ Run වෙනවා.Propagation.REQUIRES_NEW
: මේකෙන් කියන්නේ, හැමවිටම අලුත් Transaction එකක් හදලා ඒක ඇතුළේ Method එක Run කරන්න කියලයි. දැනට Transaction එකක් තිබ්බත්, ඒක Pause කරලා, අලුත් එකක් පටන් අරන්, මේ Method එක ඉවර වුණාට පස්සේ කලින් එක Resume කරනවා. මේක වැදගත් වෙන්නේ, Method එකක වැඩක් වෙනමම Transaction එකකින් වෙන්න ඕනේ නම්, අර කලින් තිබ්බ Transaction එකේ Status එකට බලපෑමක් නැතුව. උදාහරණයක් විදියට, Audit Log එකක් Save කරනවා නම්, ඒක Transaction එක Fail වුණත් Save වෙන්න ඕනේ නම් මේක පාවිච්චි කරන්න පුළුවන්.Propagation.NESTED
: Parent Transaction එකක් ඇතුළේ Sub-Transaction එකක් වගේ තමයි මේක. Sub-Transaction එක Fail වුණොත්, ඒ කොටස විතරක් Rollback වෙනවා. හැබැයි Parent Transaction එක Fail වුණොත්, Sub-Transaction එකත් Rollback වෙනවා. මේක JDBC Savepoints පාවිච්චි කරලා තමයි කරන්නේ.
2. isolation
මේකෙන් තීරණය කරන්නේ එක Transaction එකක් තව Transaction එකකින් කොච්චර දුරට හුදකලා වෙලා තියෙනවද කියන එක. මේ Isolation Levels වලින් Database Consistency එකට බලපෑමක් වෙන ආකාරයේ Problems (Dirty Reads, Non-Repeatable Reads, Phantom Reads) වළක්වා ගන්න පුළුවන්. ගොඩක්ම පාවිච්චි වෙනවා:
Isolation.READ_COMMITTED
: මේක ගොඩක් Databases වල Default එක. මේකෙන් කියන්නේ Commit කරපු Data විතරක් බලන්න පුළුවන් කියන එක. Dirty Reads වළක්වනවා.Isolation.REPEATABLE_READ
: එකම Transaction එකක් ඇතුළේ එකම Query එක කීප සැරයක් Run කළත් හැමවිටම එකම Results ටික එනවා. Non-Repeatable Reads වළක්වනවා.
ගොඩක් වෙලාවට Default Isolation Level එක ප්රමාණවත්. විශේෂ අවශ්යතාවයක් නැත්නම් මේක වෙනස් කරන්න යන්න එපා, මොකද Performance එකට බලපෑම් වෙන්න පුළුවන්.
3. readOnly
@Transactional(readOnly = true)
කියලා දැම්මොත්, ඒ Method එකෙන් Database එකට Changes කරන්න බැරි බව කියනවා. මේක Performance එකට හොඳයි, මොකද Read-only Transaction එකක් Optimisation කරන්න Database එකට පුළුවන්. Database එකට Write Operations (INSERT, UPDATE, DELETE) නොකරන Methods වලට මේක දාන්න.
4. rollbackFor
සහ noRollbackFor
අපි කලින් කතා කළා වගේ, Default විදියට @Transactional
Methods Rollback වෙන්නේ Unchecked Exceptions (RuntimeException
සහ Error
) ආවොත් විතරයි. Checked Exceptions (උදා: IOException
, SQLException
) ආවොත් Rollback වෙන්නේ නැහැ.
rollbackFor
: අපිට ඕනේ Checked Exception එකක් ආවොත් Transaction එක Rollback කරන්න නම්, මේ Property එක පාවිච්චි කරන්න පුළුවන්. උදා:@Transactional(rollbackFor = MyCustomCheckedException.class)
noRollbackFor
: සමහර Unchecked Exceptions ආවත් Transaction එක Rollback කරන්න ඕනේ නැත්නම් මේක පාවිච්චි කරන්න පුළුවන්. උදා:@Transactional(noRollbackFor = IllegalArgumentException.class)
ප්රායෝගික උදාහරණයක් (A Practical Example)
අපේ Bank System එකේ Transfer Money Example එක තව ටිකක් විස්තරාත්මකව බලමු.
// Account Entity
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String accountNumber;
private double balance;
// Getters and Setters
// Constructors
}
// Account Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
}
// Account Service
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
@Transactional(rollbackFor = InsufficientFundsException.class)
public void transferMoney(Long fromAccountId, Long toAccountId, double amount) throws InsufficientFundsException {
// Fetch accounts - Spring Data JPA will manage sessions implicitly
Account fromAccount = accountRepository.findById(fromAccountId)
.orElseThrow(() -> new RuntimeException("From account not found"));
Account toAccount = accountRepository.findById(toAccountId)
.orElseThrow(() -> new RuntimeException("To account not found"));
// Validate balance
if (fromAccount.getBalance() < amount) {
throw new InsufficientFundsException("Insufficient balance in account: " + fromAccountId);
}
// Perform debit and credit
fromAccount.setBalance(fromAccount.getBalance() - amount);
toAccount.setBalance(toAccount.getBalance() + amount);
// Save updated accounts - these changes are part of the current transaction
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
System.out.println("Transfer of " + amount + " from " + fromAccountId + " to " + toAccountId + " completed.");
}
@Transactional(readOnly = true)
public Account getAccountDetails(Long accountId) {
return accountRepository.findById(accountId).orElse(null);
}
}
// Custom Exception for Insufficient Funds (Checked Exception)
public class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}
මේ Code එකේදී transferMoney
Method එකට @Transactional(rollbackFor = InsufficientFundsException.class)
දාලා තියෙනවා. ඒ කියන්නේ InsufficientFundsException
කියන Checked Exception එක ආවත් Transaction එක Rollback වෙනවා. InsufficientFundsException
කියන්නේ අපේ Custom Checked Exception එකක්. මේක දාලා නැත්නම්, InsufficientFundsException
එක ආවොත්, fromAccount
එකෙන් සල්ලි අඩු වෙලා, ඒ වෙනස Database එකේ Commit වෙලා, toAccount
එකට සල්ලි එකතු වෙන්නේ නැති වෙන්න තිබ්බා. මේ rollbackFor
නිසා, InsufficientFundsException
එක ආපු ගමන්, Database එකේ කරපු හැම වෙනස්කමක්ම අවලංගු වෙලා, කලින් තිබ්බ තත්වෙටම එනවා. මේක තමයි ACID Properties වල Atomicity කියන එකෙන් කියන්නේ.
getAccountDetails
Method එක readOnly = true
දාලා තියෙනවා. මේකෙන් කියන්නේ මේ Method එකෙන් Database එකට කිසිම වෙනස්කමක් කරන්නේ නැහැ කියන එක. මේක Optimisation වලට ගොඩක් වැදගත්.
වැදගත් කරුණු කිහිපයක්:
- Self-invocation (Same class method calls): එකම Class එක ඇතුළේ තියෙන
@Transactional
Method එකක්, ඒ Class එකේම තව Method එකකින් Call කළොත්, Transaction Behavior එක වැඩ කරන්නේ නැති වෙන්න පුළුවන්. මොකද Spring Proxy එකෙන් Wrapper වෙන්නේ External Calls විතරයි. මේකට විසඳුම තමයි ඒ Method එක වෙනම Service එකකට කඩන එක, නැත්නම් ApplicationContext එකෙන් ඒ Bean එකම Inject කරගෙන Call කරන එක. - Service Layer එකේ
@Transactional
: ගොඩක් වෙලාවට@Transactional
Annotation එක දාන්නේ Service Layer එකට. ඒකට හේතුව Service Layer එක තමයි Business Logic එක තියෙන තැන. Database Operations කිහිපයක් එක Logic Unit එකක් විදියට Handle කරන්න ඕනේ නම්, ඒ Logic එක Service Layer එකේ තියෙන නිසා@Transactional
දාන්න හොඳම තැන තමයි Service Layer එක.
නිගමනය
දැන් ඔයාලට හොඳටම පැහැදිලි ඇති Transactions කියන්නේ මොනවාද, Spring Boot වල @Transactional
Annotation එක පාවිච්චි කරලා Database Consistency එක කොහොමද Maintain කරගන්නේ කියන එක. මේක Software Engineering වලදී අනිවාර්යයෙන්ම දැනගෙන ඉන්න ඕනේ දෙයක්. Data Loss, Inconsistent Data වගේ Problems වලින් බේරෙන්න Transaction Management ගොඩක්ම වැදගත් වෙනවා.
ඔයාලා දැන් මේ Concepts තේරුම් අරගෙන, ඔයාලගේ Project වලදී මේ @Transactional
Annotation එක පාවිච්චි කරලා බලන්න. පොඩි Sample Project එකක් හදලා මේකේ Propagation, Isolation Levels වෙනස් කරලා බලන්න. Error දාලා Rollback වෙන හැටි බලන්න. එතකොට වැඩේ හොඳටම තේරෙයි.
මේ Blog Post එක ගැන ඔයාලට මොනවා හරි ප්රශ්න තියෙනවා නම්, පහළින් Comment කරන්න. ඔයාලගේ අදහස්, යෝජනා වගේම මේ වගේ තව මොනවා ගැනද දැනගන්න කැමති කියලත් කියන්න. එහෙනම් තවත් අලුත් Technical Concept එකකින් හම්බෙමු! Good Luck!