Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
В чем выражается Rollback?
Rollback — это откат транзакции базы данных. При откате все изменения, которые были сделаны в рамках транзакции, отменяются и база возвращается в исходное состояние. Это критично для обеспечения ACID свойств.
Как работает Rollback
public class RollbackExample {
private DataSource dataSource;
public void transferMoney(int fromAccount, int toAccount, double amount)
throws SQLException {
Connection conn = dataSource.getConnection();
try {
conn.setAutoCommit(false); // начало транзакции
// Шаг 1: снять деньги
String updateFrom = "UPDATE accounts SET balance = balance - ? WHERE id = ?";
PreparedStatement ps1 = conn.prepareStatement(updateFrom);
ps1.setDouble(1, amount);
ps1.setInt(2, fromAccount);
ps1.executeUpdate();
// Шаг 2: положить деньги
String updateTo = "UPDATE accounts SET balance = balance + ? WHERE id = ?";
PreparedStatement ps2 = conn.prepareStatement(updateTo);
ps2.setDouble(1, amount);
ps2.setInt(2, toAccount);
ps2.executeUpdate();
// Если всё успешно
conn.commit(); // фиксируем изменения
} catch (SQLException e) {
// При ошибке
conn.rollback(); // откатываем оба UPDATE'а
throw e;
} finally {
conn.close();
}
}
}
Выражение Rollback в базе данных
Пример: операция без Rollback (ПЛОХО)
-- Состояние до:
-- Account 1: balance = 1000
-- Account 2: balance = 500
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- balance = 900
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- balance = 600
-- Ошибка! Network failure
COMMIT; -- не выполнится
-- Что произошло: ROLLBACK был неявный
-- Account 1: balance = 1000 (откачено)
-- Account 2: balance = 500 (откачено)
Пример: явный Rollback (ПРАВИЛЬНО)
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- Ошибка!
ROLLBACK; -- явный откат
-- Результат: обе операции отменены
-- Account 1: balance = 1000
-- Account 2: balance = 500
Механизм Rollback в JDBC
public class JDBCRollback {
public static void main(String[] args) throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/db", "user", "pass");
try {
// Отключаем auto-commit для контролируемых транзакций
conn.setAutoCommit(false);
// Выполняем операции
Statement stmt = conn.createStatement();
stmt.execute("INSERT INTO users (name) VALUES ('John')");
stmt.execute("INSERT INTO orders (user_id, amount) VALUES (1, 100)");
// Все прошло успешно
conn.commit(); // фиксируем обе INSERT'ы
} catch (SQLException e) {
// Ошибка - отменяем обе INSERT'ы
conn.rollback();
System.out.println("Transaction rolled back: " + e.getMessage());
} finally {
conn.close();
}
}
}
Rollback при исключениях
public class RollbackOnException {
private UserService userService;
private OrderService orderService;
private DataSource dataSource;
public void createUserWithOrder(User user, Order order) throws Exception {
Connection conn = dataSource.getConnection();
try {
conn.setAutoCommit(false);
userService.save(user, conn); // INSERT user
orderService.save(order, conn); // INSERT order
// Если orderService выбросил исключение
// rollback отменит INSERT user
conn.commit();
} catch (Exception e) {
conn.rollback(); // отменяем всё
throw e;
} finally {
conn.close();
}
}
}
Rollback vs Commit
| Операция | Результат | Переход в режим | Использование |
|---|---|---|---|
| COMMIT | Фиксирует все изменения | Новая транзакция | После успешных операций |
| ROLLBACK | Отменяет все изменения | Новая транзакция | При ошибке |
Savepoint — частичный Rollback
public class SavepointExample {
public static void main(String[] args) throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/db", "user", "pass");
try {
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
stmt.execute("INSERT INTO accounts (id, name) VALUES (1, 'Alice')");
stmt.execute("INSERT INTO accounts (id, name) VALUES (2, 'Bob')");
// Создаем точку сохранения
Savepoint sp1 = conn.setSavepoint("sp1");
stmt.execute("INSERT INTO accounts (id, name) VALUES (3, 'Charlie')");
stmt.execute("UPDATE accounts SET balance = 1000"); // Ошибка!
// Откатываем только до sp1
conn.rollback(sp1);
// Теперь у нас есть Alice и Bob
// Charlie откачена
conn.commit();
} catch (SQLException e) {
conn.rollback(); // полный откат если что-то не так
} finally {
conn.close();
}
}
}
Spring Transactional Rollback
@Service
public class UserService {
@Transactional // Spring управляет транзакцией
public void registerUser(User user) throws Exception {
userRepository.save(user);
// Если это выбросит исключение
sendWelcomeEmail(user);
// Spring автоматически вызовет rollback
// пользователь не будет в БД
}
@Transactional(rollbackFor = PaymentException.class)
public void processPayment(Payment payment) {
paymentRepository.save(payment);
try {
chargeCard(payment);
} catch (PaymentException e) {
// Spring откатит транзакцию
throw e;
}
}
}
Когда Rollback НЕ происходит
// ПЛОХО: Checked исключение — НЕ откатывает
@Transactional
public void processData() throws IOException { // checked
repository.save(data);
readFile(); // IOException не откатит транзакцию!
}
// ПРАВИЛЬНО: RuntimeException откатывает
@Transactional
public void processData() {
repository.save(data);
if (someCondition) {
throw new RuntimeException("Error"); // откатит
}
}
// ПРАВИЛЬНО: явно указать rollbackFor
@Transactional(rollbackFor = IOException.class)
public void processData() throws IOException {
repository.save(data);
readFile(); // теперь откатит
}
Redo Log и Undo Log
Внутри БД откат работает так:
Redo Log (для восстановления после краша):
INSERT user 'John' at offset 1000
UPDATE account 1 SET balance=900 at offset 1005
Undo Log (для откката транзакции):
IF ROLLBACK: DELETE user 'John' from offset 1000
IF ROLLBACK: UPDATE account 1 SET balance=1000 from offset 1005
При ROLLBACK: выполняются операции из Undo Log в обратном порядке
Пример: Rollback при deadlock
public class DeadlockRollback {
@Transactional(retry = 3)
public void transfer(int from, int to, double amount) {
// Thread 1:
// UPDATE accounts SET balance=balance-100 WHERE id=1
// UPDATE accounts SET balance=balance+100 WHERE id=2
// Thread 2:
// UPDATE accounts SET balance=balance+50 WHERE id=2
// UPDATE accounts SET balance=balance-50 WHERE id=1
// DEADLOCK! БД откатит одну из транзакций
// Spring перезапустит метод (retry)
}
}
Лучшие практики
// 1. Минимизируй размер транзакции
@Transactional
public void quickOperation() {
repository.save(data); // быстро
// не делай долгие операции
}
// 2. Обрабатывай исключения явно
@Transactional
public void processWithError() {
try {
repository.save(data);
} catch (DataException e) {
log.error("Failed to save", e);
throw new RuntimeException(e); // откатит
}
}
// 3. Используй read-only для чтения
@Transactional(readOnly = true)
public List<User> getAllUsers() {
return repository.findAll();
}
// 4. Правильный уровень изоляции
@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateBalance(int accountId, double amount) {
// защита от грязного чтения
}
Вывод
Rollback выражается как:
- JDBC: conn.rollback() отменяет все UPDATE/INSERT/DELETE
- SQL: ROLLBACK команда откатывает транзакцию
- Spring: @Transactional автоматически откатывает на исключение
- БД: Undo log отменяет операции в обратном порядке
Рollback гарантирует что если транзакция не завершилась успешно — база остаётся в консистентном состоянии (A в ACID).