Как работает транзакция при добавлении списка пользователей в Java
Что проверяют
Понимаете ли вы, что вставка списка это одна логическая операция: либо записались все пользователи, либо ни одного. Следом обычно спрашивают, как это выглядит в Spring и что будет, если в середине списка встретится дубль email.
Как отвечать
Начните с уровня JDBC, на нём всё видно без магии:
- автокоммит: по умолчанию
Connectionкоммитит каждый запрос отдельно, поэтому сначалаsetAutoCommit(false); - батч: один
PreparedStatement, в циклеsetStringиaddBatch(), затемexecuteBatch(). Меньше сетевых обращений к базе; - завершение:
commit()при успехе,rollback()вcatch, соединение и statement в try-with-resources, а не ручнойclose()вfinally.
В реальном проекте это Spring: метод с @Transactional, внутри saveAll(). Откат по умолчанию происходит только на RuntimeException и Error, проверяемое исключение транзакцию закоммитит, если не указать rollbackFor. Аннотация работает через прокси, поэтому вызов из соседнего метода того же класса транзакцию не откроет.
Про скорость: saveAll() без настройки шлёт по одному INSERT. Нужен hibernate.jdbc.batch_size, а при GenerationType.IDENTITY Hibernate батчинг вставок отключает, выручает SEQUENCE. Драйверы тоже помогают: reWriteBatchedInserts=true у PostgreSQL и rewriteBatchedStatements=true у MySQL склеивают батч в многострочный INSERT.
Если список огромный, одна транзакция на миллион строк долго держит блокировки. Тогда режут на пачки по несколько тысяч и заранее решают, что делать с частично загруженными данными.
Где спотыкаются
Ловят исключение внутри @Transactional метода и не пробрасывают его. Spring не видит ошибки и коммитит то, что успело записаться.
Путают батч и транзакцию. executeBatch() при включённом автокоммите атомарности не даёт, это только меньше обращений к базе.
Не знают, что в PostgreSQL после ошибки транзакция становится прерванной: все следующие команды падают до rollback. Поэтому «пропустить плохую строку и продолжить» без точки сохранения (setSavepoint()) не получится.