Как управлять транзакциями в Java при работе с БД?

Ответ

Управление транзакциями в Java осуществляется через JDBC, JPA/Hibernate или Spring @Transactional. Ключевые принципы: атомарность, согласованность, изоляция, долговечность (ACID).

1. JDBC (низкоуровневый API)

Connection conn = dataSource.getConnection();
try {
    conn.setAutoCommit(false); // Начало транзакции

    PreparedStatement stmt = conn.prepareStatement(
        "UPDATE accounts SET balance = ? WHERE id = ?"
    );
    stmt.setBigDecimal(1, newBalance);
    stmt.setLong(2, accountId);
    stmt.executeUpdate();

    conn.commit(); // Фиксация изменений
} catch (SQLException e) {
    conn.rollback(); // Откат при ошибке
    throw e;
} finally {
    conn.setAutoCommit(true);
    conn.close();
}

2. JPA/Hibernate

EntityTransaction tx = entityManager.getTransaction();
try {
    tx.begin();
    Account account = entityManager.find(Account.class, accountId);
    account.setBalance(newBalance);
    tx.commit();
} catch (Exception e) {
    if (tx.isActive()) {
        tx.rollback(); // Явный откат
    }
    throw e;
}

3. Spring @Transactional (рекомендуемый подход)

@Service
public class AccountService {
    @Transactional(isolation = Isolation.READ_COMMITTED,
                   rollbackFor = {SQLException.class})
    public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
        // Бизнес-логика
        // При исключении — автоматический rollback
    }
}

Ключевые аспекты:

  • Уровни изоляции: READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE.
  • Распространённые проблемы: грязное чтение, неповторяемое чтение, фантомное чтение.
  • Паттерны: оптимистичные/пессимистичные блокировки, компенсирующие транзакции (Saga).

Ответ 18+ 🔞

А, ну это же про транзакции, ёпта! Ну, классика жанра, блядь. Все эти ACID-ы, которые в рот меня чих-пых, пока на проде гоняешь. Слушай, давай разжую, как есть, без этой охуевшей академической пыли.

Первое, JDBC — это как голыми руками в говне ковыряться, блядь. Низкоуровневое такое, ручное управление. Весь этот цирк с setAutoCommit(false), commit() и rollback(). Один косяк — и всё, приехали, сидишь и думаешь, какого хуя данные не обновились, а они просто в транзакции висят, сука.

Connection conn = dataSource.getConnection();
try {
    conn.setAutoCommit(false); // Всё, понеслась, блядь!

    PreparedStatement stmt = conn.prepareStatement(
        "UPDATE accounts SET balance = ? WHERE id = ?"
    );
    stmt.setBigDecimal(1, newBalance);
    stmt.setLong(2, accountId);
    stmt.executeUpdate();

    conn.commit(); // Уфф, зафиксировали, живём!
} catch (SQLException e) {
    conn.rollback(); // Всё нахуй, откатываем! Пиздец, ошибка!
    throw e;
} finally {
    conn.setAutoCommit(true);
    conn.close(); // Не забудь закрыть, а то коннектов дохуя сожрёшь!
}

Работает, конечно, но писать это каждый раз — терпения ебать ноль. Один finally забыл — и у тебя утечка, привет.

Дальше, JPA/Hibernate — уже получше, но тоже своя банда. Тут уже EntityTransaction. Начинаешь (begin()), делаешь дела, фиксируешь (commit()). Словил исключение — rollback(), если транзакция ещё жива. Главное — не проёбывайся с проверкой isActive(), а то получишь ещё один пиздец поверх первого.

EntityTransaction tx = entityManager.getTransaction();
try {
    tx.begin();
    Account account = entityManager.find(Account.class, accountId);
    account.setBalance(newBalance);
    tx.commit(); // Вроде ок!
} catch (Exception e) {
    if (tx.isActive()) {
        tx.rollback(); // Ага, ловим и откатываем, хитрая жопа!
    }
    throw e;
}

Уже симпатичнее, но всё равно рутина, блядь. Весь этот try-catch — у меня волнение ебать от одного вида.

И наконец, Spring @Transactional — это просто песня, ёбана! Аннотацию кинул — и всё, блядь. Фреймворк сам обернёт твой метод в транзакцию, сам закоммитит, сам откатит при косяке. Красота, да и только!

@Service
public class AccountService {
    @Transactional(isolation = Isolation.READ_COMMITTED,
                   rollbackFor = {SQLException.class})
    public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
        // Делай что хочешь тут
        // Кинется исключение — всё само откатится, как по маслу!
    }
}

Вот это я понимаю — элегантно, сука. Нехуй мозги парить.

А теперь про подводные ебучки, которые всех накрывают медным тазом:

  • Уровни изоляции. Это пиздец, чувак. READ_UNCOMMITTED — это когда читаешь даже неподтверждённые данные, как будто подглядываешь в замочную скважину. READ_COMMITTED — уже норм, только коммитнутое видишь. REPEATABLE_READ и SERIALIZABLE — это когда всё серьёзно, но и тормозят соответственно, овердохуища.
  • Проблемы. Грязное чтение, неповторяемое чтение, фантомы... Это не про духов, а про то, как данные у тебя из-под носа уплывают, если изоляцию криво настроил. Чувствуешь подозрение, блядь? Вот-вот.
  • Паттерны. Оптимистичная блокировка — это как надеяться, что никто твою запись не тронет (версию проверяешь). Пессимистичная — это сразу захватываешь лок, как будто говоришь: «Отойди, сука, это моё!». А Saga — это для распределённых систем, когда одна большая транзакция разбивается на кучу маленьких с компенсациями, если что-то пошло не так. Сложна, блядь, но жить можно.

Короче, суть в чём: не выёбывайся, используй @Transactional от Spring, если можешь. И думай головой, какой уровень изоляции тебе нужен, а то потом будешь искать, куда делись твои данные, а они, блядь, фантомом прочитались и испарились. В рот меня чих-пых!