1. Навіщо потрібні транзакції
Дуже часто при роботі з базою даних виникає ситуація, коли потрібно виконати багато різних дій, але сенс вони мають лише разом.
Наприклад, ми пишемо банківське ПЗ, яке має зробити три речі:
- Списати гроші з рахунку клієнта
- Додати гроші на рахунок одержувача
- Записати дані про проведення до “журналу проведень”
Якщо під час виконання будь-якої з цих дій виникне помилка, то решту потрібно також скасовувати. Не можна ж списати гроші у клієнта та не додати їх одержувачу? Ну або додати одержувачу, але не списати у клієнта?
Так ось, таке логічне угруповання різних дій до одної називається транзакцією. Іншими словами, транзакція — це група дій, які мають бути виконані тільки всі разом. Якщо будь-яка дія не виконалася або виконалася з помилкою, всі інші дії повинні бути скасовані.
У транзакції зазвичай є три стани:
- initial state — стан системи перед виконанням групи дій
- success state — стан після виконання групи дій
- failed state — щось пішло не так
При цьому зазвичай є три команди:
- begin/start — виконується перед початком логічної групи дій
- commit — виконується після групи дій транзакції
- rollback — запускає процес повернення системи з failed state до initial state
Працює це так.
Спочатку потрібно відкрити транзакцію — викликати метод begin() або start(). Виклик цього методу означає стан системи, до якого ми спробуємо повернутися, якщо щось не піде.
Потім виконуються всі дії, які об'єднані в логічну групу — транзакцію.
Тому викликається метод commit(). Його виклик означає кінець логічної групи дій, а також зазвичай запускає процес реалізації цих дій на практиці.
Згадай, як ми писали щось у FileWriter: спочатку все, що ми написали, зберігається в пам'яті, а потім під час виклику методу flush() всі дані з буфера у пам'яті пишуться на диск. Ось цей flush() — це і є коміт транзакції.
Ну а якщо під час роботи транзакції сталася помилка, то потрібно ініціювати процес повернення до стартового стану. Цей процес називається rollback(), і за нього зазвичай відповідає однойменний метод.
Грубо кажучи, є два способи завершити транзакцію:
- COMMIT — підтверджуємо усі внесені зміни
- ROLLBACK — відкочуємо всі внесені зміни
2. Транзакції в JDBC
Практично кожна СУБД вміє працювати із транзакціями. Тож і у JDBC підтримка цієї справи також є. Реалізовано все дуже просто.
По-перше, кожен виклик методу execute() об'єкта Statement виконується в окремій транзакції. Для цього Connection має параметр AutoCommit. Якщо він виставлений у true, то commit() буде викликатись після кожного виклику методу execute().
По-друге, якщо ти хочеш виконати кілька команд в одній транзакції, зробити це можна так:
- відключаємо AutoCommit
- викликаємо наші команди
- викликаємо метод commit() явно
Виглядає це дуже просто:
connection.setAutoCommit(false);
Statement statement = connection.createStatement();
int rowsCount1 = statement.executeUpdate("UPDATE employee SET salary = salary+1000");
int rowsCount2 = statement.executeUpdate("UPDATE employee SET salary = salary+1000");
int rowsCount3 = statement.executeUpdate("UPDATE employee SET salary = salary+1000");
connection.commit();
Якщо під час роботи методу commit() на сервері відбудеться помилка, то SQL-сервер скасує всі три дії.
Але бувають ситуації, коли помилка виникає ще на стороні клієнта, і ми так і не дійшли до виклику методу commit():
connection.setAutoCommit(false);
Statement statement = connection.createStatement();
int rowsCount1 = statement.executeUpdate("UPDATE employee SET salary = salary+1000");
int rowsCount2 = statement.executeUpdate("UPDATE employee SET salary = salary+1000");
int rowsCount3 = statement.executeUpdate("UPDATE кілька помилок призведуть до виключення");
connection.commit();
Якщо під час роботи одного executeUpdate() відбудеться помилка, то метод commit() так і не викличеться. Щоб відкотити всі дії, потрібно викликати метод rollback(). Зазвичай це робиться так:
try{
connection.setAutoCommit(false);
Statement statement = connection.createStatement();
int rowsCount1 = statement.executeUpdate("UPDATE employee SET salary = salary+1000");
int rowsCount2 = statement.executeUpdate("UPDATE employee SET salary = salary+1000");
int rowsCount3 = statement.executeUpdate("UPDATE кілька помилок призведуть до вимкнення");
connection.commit();
}
catch (Exception e) {
connection.rollback();
}
3. Точки збереження
З появою JDBC 3.0 з'явилася можливість ефективно працювати з відкатом транзакції. Тепер можна встановлювати точки збереження — save points, а під час виклику операції rollback() відкочуватися до конкретної точки збереження.
Щоб зберегтися, потрібно створити точку збереження. Робиться це командою:
Savepoint save = connection.setSavepoint();
Повернення до точки збереження робиться командою:
connection.rollback(save);
Давай спробуємо додати точку збереження перед проблемною командою:
try{
connection.setAutoCommit(false);
Statement statement = connection.createStatement();
int rowsCount1 = statement.executeUpdate("UPDATE employee SET salary = salary+1000");
int rowsCount2 = statement.executeUpdate("UPDATE employee SET salary = salary+1000");
Savepoint save = connection.setSavepoint();
try{
int rowsCount3 = statement.executeUpdate("UPDATE кілька помилок призведуть до вимкнення");
}
catch (Exception e) {
connection.rollback(save);
}
connection.commit();
}
catch (Exception e) {
connection.rollback();
}
Ми організували щось типу вкладених транзакцій за допомогою додавання save-point перед викликом проблемного методу, і повернення до збереженого стану за допомогою виклику методу rollback(save) .
Так, це дуже схоже на save/load в іграх.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ