Навіщо потрібні транзакції
Дуже часто при роботі з базою даних виникає ситуація, коли потрібно виконати багато різних дій, але вони мають сенс тільки разом.
Наприклад, ми пишемо банківське ПЗ, яке має зробити три речі:
- Списати гроші з рахунку клієнта
- Додати гроші на рахунок отримувача
- Записати дані про проведення у “журнал проводок”
Якщо під час виконання будь-якої з цих дій виникне помилка, то решту двох потрібно також скасовувати. Не можна ж списати гроші у клієнта та не додати їх одержувачу? Ну чи додати одержувачу, але не списати у клієнта?
Так от, таке логічне угруповання різних дій в одне називається транзакцією . Іншими словами, транзакція – це група дій, які мають бути виконані лише всі разом . Якщо будь-яка дія не виконалася або виконалася з помилкою, всі інші дії повинні бути скасовані.
У транзакції зазвичай є три стани:
- initial state - стан системи перед виконанням групи дій
- success state - стан після виконання групи дій
- failed state - щось пішло не так

При цьому зазвичай є три команди:
- begin/start - виконується перед початком логічної групи дій
- commit - виконується після групи дій транзакції
- rollback - запускає процес повернення системи з failed state в initial state
Працює так.
Спочатку потрібно відкрити транзакцію – викликати метод begin() або start() . Виклик цього методу означає стан системи, якого ми спробуємо повернутися, якщо щось піде негаразд.
Потім виконуються всі дії, які об'єднані в логічну групу транзакцію.
Потім викликається метод commit() . Його виклик позначає кінець логічної групи дій, і навіть зазвичай запускає процес реалізації цих дій практично.
Згадай, як ми писали щось у FileWriter: спочатку все, що ми написали, зберігається у пам'яті, та був за виклику методу flush() всі дані з буфера у пам'яті пишуться на диск. Ось цей flush() — це коміт транзакції.
Ну, а якщо під час роботи транзакції сталася помилка, то потрібно ініціювати процес повернення до стартового стану. Цей процес називається rollback() і за нього зазвичай відповідає однойменний метод.
Грубо кажучи, є два способи завершити транзакцію:
- COMMIT - підтверджуємо всі внесені зміни
- ROLLBACK - відкочуємо всі внесені зміни
Транзакції у 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();
}
Крапки збереження
З появою 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 в іграх.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ