TGViewer
Канал с кабанчиком Канал с кабанчиком @boar_channel · 118 subscribers
Post #40 364
У сочетания Kotlin и Spring есть одна старая проблема, на которой часто спотыкаются разработчики.

Спринговая аннотация @Transactional по умолчанию работает следующим образом - любой RuntimeException или Error приводит к роллбеку, но Exception - нет. Такое поведение - прямое наследие EJB (Enterprise JavaBeans), где checked-исключения (наследники Exception) считались ошибками бизнес-логики, а unchecked (наследники RuntimeException) - неожиданными системными/техническими ошибками. Поэтому ожидаемые бизнесовые ошибки предлагалось обрабатывать явно и не отказывать транзакцию автоматически.

В современном мире такое соглашение редко соблюдается, к тому же даже в стандартной библиотеке полно checked-исключений, которые нельзя назвать бизнесовыми (вроде IOException). Разумеется, в Spring это настраивается, у @Transactional есть гибкие способы точечно подтюнить это поведение с помощью параметров rollbackFor/rollbackForClassName/noRollbackFor/noRollbackForClassName. Но даже в Java многие не раз попадались на этот нюанс, или не зная о нем, или забыв.

А в Kotlin всё становится еще интереснее, ведь в Kotlin нет такого понятия как checked-exception. Да, код на Kotlin работает с кодовой базой Java и может взаимодействовать с наследниками Exception, но компилятор не заставит обработать такое исключение, как произошло бы в Java. Как так? А всё потому что checked-исключения - это фича именно языка Java и его компилятора, а не самой JVM. На уровне байткода между этими исключениями нет никакой разницы, и в рантайме они ведут себя одинаково. Только компилятор Java заставляет как-то по-особенному обрабатывать checked-исключения. А вот компилятор Kotlin нет. И в случае @Transactional это большой подвох, потому что разработчик на Kotlin уже понятия не имеет, является ли его исключение наследником Exception или RuntimeException. За исключением работы с @Transactional нет никакой разницы, поэтому и нет смысла задумываться о различиях.

Разумеется, разработчик, который всё-таки знает о таком поведении (а скорее, уже однажды попавшийся на ситуацию с не откатившейся транзакцией), просто напишет @Transactional(rollbackOn = Exception::class.java). Но блин, это же нужно делать везде. Где-то наверняка забудем. К тому же, выглядит уже грязновато.

И до недавних пор адекватного способа решить эту проблему глобально - не было. Но в Spring 6.2 (и Spring Boot 3.4, соответственно) наконец-то появилось решение, закрыв собой ишью аж от 2019 года. Теперь можно задать глобальное поведение по умолчанию вот таким образом - @EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS), а какие-то дополнительные глобальные тюнинги можно выполнять, настраивая бин AnnotationTransactionAttributeSource. Разумеется, команда Spring не может сделать это новым дефолтом, потому что это сломает обратную совместимость, так что фича исключительно opt-in. Но они рекомендуют самостоятельно включить такой режим, и в Java, и, тем более, в Kotlin-приложениях.
  • 👍 8
  • ❤ 3
More from @boar_channel
  1. Jan 27, 2026Не смог пройти мимо тренда, извините
  2. Jan 24, 2026video post
  3. Jan 20, 2026В PostgreSQL есть одна интересная фича под названием exclusion constraint. Она позволяет д…
  4. Jan 11, 2026А вот тут https://arxiv.org/pdf/2512.14982 исследователи утверждают, что простое повторени…
  5. Jan 11, 2026А вот тут https://arxiv.org/pdf/2512.14982 исследователи утверждают, что простое повторени…
  6. Jan 1, 2026photo post
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →