Привет! В этой статье я бы хотел рассказать, как Spring'овая аннотация @Transactional ведет себя при возникновении исключений. Про это немало написано, в том числе на Хабре. Например, тут или тут. Поэтому, чтобы не повторяться, я не буду подробно расписывать как и почему исключения влияют на откат транзакций, а вместо этого просто покажу несколько примеров. Если коротко, то Spring обрабатывает исключения в методах, помеченных @Transactional, следующим образом: RuntimeException'ы приводят к откату транзакции, checked exception'ы не приводят; RuntimeException'ы приводят к откату транзакции в момент, когда exception пересекает границы @Transactional-метода. Даже если вы перехватите это исключение выше по стеку, транзакция все равно откатится; Этим поведением можно управлять через атрибуты rollbackFor / noRollbackFor у аннотации @Transcational. Что будет, если мы передадим для сохранения список Item'ов, у которых 0-й и 2-й элемент НЕ являются BAD_ITEM'ами, а 1-й - является?  Ничего не будет сохранено, поскольку RuntimeException в методе saveItem откатит транзакцию; Будет сохранен только 0-й элемент; Будет сохранен 0-й и 2-й элемент. Правильный ответ - 3 (ну, как правило, подробнее - ниже). Когда мы из компонента так вызываем метод того же компонента, это просто вызов внутреннего метода, а не метода прокси-объекта. Он попросту игнорирует аннотацию @Transactional у saveItem и не будет создавать новый транзакционный контекст. Почему так происходит можно прочитать в статьях, на которые я ссылался в начале. А значит RuntimeException не пересечет границу @Transactional-метода. Если мы используем compile-time weaving, либо если мы перепишем код, чтобы вызов происходил через inner.saveItem, то правильным ответом будет 1. Тот же вопрос: что будет, если мы передадим для сохранения список Item'ов, у которых 0-й и 2-й элемент НЕ являются BAD_ITEM'ами, а 1-й - является? Ничего не будет сохранено; Будет сохранен только 0-й элемент; Будет сохранен 0-й и 2-й элемент. Правильный ответ, по-прежнему, 3. Теперь транзакция с RuntimeException'ом откатится, но этот код создает 3 транзакции. Остальные 2 будут закоммичены. Все то же самое, но теперь метод saveItems тоже @Transactional: Ничего не будет сохранено; Будет сохранен только 0-й элемент; Будет сохранен 0-й и 2-й элемент. А вот теперь правильный ответ - 1. По умолчанию, @Transactional использует Propagation.REQUIRED, который приведет к тому, что saveItem будет использовать транзакцию, открытую для saveItems. При ошибке она будет помечена как rollbackOnly. А теперь добавим в контроллер ограничение, что RuntimeException'ы не должны откатываться. Ничего не будет сохранено; Будет сохранен только 0-й элемент; Будет сохранен 0-й и 2-й элемент. Правильный ответ все еще 1. noRollbackFor влияет только на аннотируемый метод, его поведение не "наследуется" компонентами ниже по стеку вызовов, даже если они используют ту же транзакцию. Поэтому saveItem пометит транзакцию как rollbackOnly. Перенесем noRollbackFor на saveItem. А у контроллера уберем код по перехватыванию RuntimeException. Ничего не будет сохранено Будет сохранен только 0-й элемент; Будет сохранен 0-й и 2-й элемент. Правильный ответ - 1. Да, теперь saveItem не откатывает транзакцию, но с самим-то RuntimeException'ом он ничего не делает. Exception пролетит через контроллер, и  уже у его @Transactional-метода вызовет откат транзакции. Пометим оба @Transactional-метода так, чтобы они не откатывали runtime-исключения: Ничего не будет сохранено Будет сохранен только 0-й элемент; Будет сохранен 0-й и 2-й элемент. Правильный ответ - 2. Транзакция не откатится, но из-за исключения обработка списка остановится. Тот же эффект будет, если вообще убрать аннотации @Transactional, либо если эту аннотацию убрать только у контроллера. У Spring Data в интерфейсе CrudRepository есть такой вот метод:  void deleteAllById(Iterable<? extends ID> ids) Что если ему на вход передать среди существующих идентификаторов один несуществующий? Я бы ожидал, что он удалит те, что есть, и проигнорирует те, которых нет (ну, как и обычный оператор delete в SQL). И да, Spring Data, именно так и сделает, но только если вы используете Spring Data 3. А ему на данный момент что-то около полугода. Если вы используете более раннюю версию, вы получите ошибку EmptyResultDataAccessException (Spring Data писали странные люди, и да, пошла она к черту эта ваша обратная совместимость). Что будет, если в середине списка несуществующий айдишник в Spring Data 2.x? Транзакция полностью откатится. У Repository модифицирующие методы сами помечены как @Transactional, поэтому фактически этот пример эквивалентен примеру номер 4. Вы можете в своем репозитории переопределить метод deleteAllById, указав уже ему noRollbackFor = EmptyResultDataAccessException.class, но это плохая идея. deleteAllById просто в цикле вызывает deleteById. Он тоже помечен как @Transactional, но это неважно, как мы выяснили в примере номер 1. Когда deleteById сгенерирует исключение, транзакция не откатится, но цикл прервется. В итоге, вы окажетесь в ситуации примера номер 6, когда половина Item'ов удалилась, а половина - нет. Выход - либо вместо deleteAllById проверять на стороне сервиса, что Item существует, а потом уже вызывать deleteById, либо удалять модифицирующим JPQL-запросом. Что будет если какого-то из айдишников нет? Ну, во-первых, автора такого пул-реквеста, скорее всего, побьют на code-review. Возможно, ногами.  Ответ - сработает то правило, которое содержит ближайшего родителя брошенного Exception'а. В нашем случае - noRollbackForClassName, поэтому часть Item'ов удалится, часть - нет. Если родителя нет среди правил, будет использоваться дефолтное поведение (RuntimeException и Error откатываем, checked - не трогаем). Есть еще совсем упоротый вариант, когда написано что-то вроде  rollbackFor = RuntimeException.class, noRollbackFor = RuntimeException.class. Spring разрешит сделать и это, к сожалению. Судя по исходному коду, всегда сработают noRollback-правила. Атрибуты rollbackFor / noRollbackFor управляют только поведением транзакции в случае возникновения исключений. Сами исключения по-прежнему пробрасываются выше по стеку; Атрибуты rollbackFor / noRollbackFor не наследуются @Transactional-методами ниже по стеку вызовов, даже если используется Propagation.REQUIRED; Если вам всерьез приходится разбираться и активно пользоваться всем этим, то задумайтесь, а нужны ли в вашем конкретном случае транзакции вообще и транзакции, управляемые контейнером, в частности. Переход на ручную работу с транзакциями может упростить код, по сравнению с логикой, построенной на разных типах исключений.