Как создать транзакцию к базе данных без использования аннотации @Transactional
Что проверяют
Понимание, что @Transactional это обёртка над границей транзакции, а не магия фреймворка, и умение расставить эту границу руками: autoCommit, commit, rollback, возврат соединения в исходное состояние.
Как отвечать
Три уровня, от низкого к высокому.
1. Чистый JDBC. Транзакция это состояние соединения. Все операции обязаны идти через один и тот же Connection, иначе это разные транзакции.
try (Connection cn = dataSource.getConnection()) {
boolean auto = cn.getAutoCommit();
cn.setAutoCommit(false);
try {
try (PreparedStatement ps = cn.prepareStatement("insert into t(col) values (?)")) {
ps.setString(1, "value");
ps.executeUpdate();
}
cn.commit();
} catch (SQLException | RuntimeException e) {
cn.rollback();
throw e;
} finally {
cn.setAutoCommit(auto);
}
}Детали, которые отличают правильный ответ от списанного:
rollback()стоит вcatch, а не вfinally, и исключение пробрасывается дальше, а не глотается черезprintStackTrace();setAutoCommitвосстанавливается перед возвратом соединения в пул, иначе следующий потребитель получит соединение с чужими настройками;- уровень изоляции задаётся
cn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED)до начала работы; - частичный откат делается через
Savepoint sp = cn.setSavepoint()иcn.rollback(sp).
2. Spring, программно. TransactionTemplate работает поверх того же PlatformTransactionManager, который стоит за аннотацией:
transactionTemplate.execute(status -> {
repository.save(entity);
if (needRollback) status.setRollbackOnly();
return null;
});Параметры (propagation, isolation, timeout, readOnly) задаются через TransactionDefinition. Совсем вручную: TransactionStatus st = tm.getTransaction(def), затем tm.commit(st) или tm.rollback(st).
3. JPA напрямую. em.getTransaction().begin() ... commit() для resource-local, либо UserTransaction при JTA в контейнере.
Когда это реально нужно: транзакция должна быть короче метода (аннотация растягивает её на весь вызов, включая внешние HTTP-запросы), нужна отдельная транзакция на каждую пачку в батче, вызов идёт из того же класса (самовызов прокси не перехватывает, и @Transactional там молча не работает), либо код исполняется вне Spring-контекста.
Где спотыкаются
Ловят исключение, логируют и идут дальше, не вызвав rollback(): соединение возвращается в пул с открытой транзакцией и держит блокировки, а баг проявляется в другом месте и позже.