Как стать автором
Обновить

Комментарии 21

Получается, что метод JpaRepository::saveAndFlush даёт нам доступ к нижним уровням API, хотя задача фреймворка как раз в сокрытии низкоуровневых подробностей (ситуация чем-то похожа на историю с Unsafe в JDK).

Всё же иногда возникают ситуации когда действительно нужно принудительно сбросить кэш операций в БД и этот момент упрощает работу в таком случае.

Согласен, видел и такое. Нередко этим лечились некоторые ошибки в самом Хибернейте или неправильный маппинг с нашей стороны. Как правило, происходило примерно следующее:


  • тестировщики завели баг
  • при отладке оказалось, что данные, которые мы ожидаем в базе на определённом шаге сложного сценария, в БД не появляются
  • сделали флаш и всё заработало.

Это рабочая, но всё же немного костыльная схема: правильное решение, ИМХО, это правильный маппинг или исправление в самом Хибернейте.


В крупном и сложном проекте спустя несколько лет после того, как конкретный флаш был прописан в коде, очень сложно сказать действительно ли он там нужен (и без него ну никак), или это костыль, которым на скорую руку закрыли ошибку на юате.

Для того чтобы проверить нужен flush или нет — есть тесты.
Если он не нужен, то тесты после его убираения не упадут.
Если тесты, конечно, вообще есть и есть тест который проверяет конкретную ситуацию в которой flush был необходим.

А если flush используется как синхронизатор (гарантировать, что не будет гонки между записью в базу и последующим кодом) и баг в его отсутствие "плавающий"? Или на этот случай есть варианты лучше?

Тут к с любым плавающим багом — нужно разбираться в чём его причина.
Но тут проблема в том что это занимает больше времени и в случае когда баг редок и/или не критичен, то проще забить.
Ну или использовать костыль, который может бить по производительности всё время, но в рельутате это всё равно будет дешевле прямого решения проблемы.

Так вот я и спрашиваю: если flush здесь (в конкретном случае состояния гонки) — это костыль, а не решение, то что будет решением? Хотя бы теоретически, что есть смысл рассматривать?

Если у вас уже есть понимание что бага происходит из-за состояния гонки, то нужно копать дальше — что приводит к состоянию гонки.
Может проблема в порядке операций/запросов. Может в кеше хибера. Может в чём-то ещё.


Для того чтобы решить нужен ли flush вообще и можно ли его убрать или заменить на что-то другое — нужно разобраться в первопричине ошибки. А для этого для начала сделать её не "плавающей", а "воспроизводимой". И потом уже смотреть на причины. И уже в зависимости от точно выявленных причин можно начинать решать костыль ли flush или нет.


Ниже рассмотрен вариант когда нативный запрос не видит данные которые "вставлялись" через Hibernate, и он их не увидит потому что Hibernate ещё не сбросил свой кэш операций этих записей в БД и там их ещё реально ещё нет.
В этому случае flush это уже не такой уж и костыль, особенно если работать точно нужно в одной транзакции (а чаще всего так и нужно).
Хотя тут можно попробовать организовать вложенные транзакции и вставку делать в более глубокой транзакции, по отношению к той где будет использован нативный запрос. Будет ли это быстрее (по работе) и проще (в реализации) — тот ещё вопрос.

На проекте, в котором выловил этот пример, интеграционными тестами был покрыт только ядерный функционал (специфика проекта), в который именно этот флаш не попал. Конечно, было бы гораздо проще с полным интеграционным покрытием.

Был случай когда мешал hibernate с нативщиной, в конце транзакции как раз и был нативный sql, который указывает на сущность, создаваемую hibernate'ом. Только вот до тех пор пока не будет она создана, нативный будет валиться из-за несуществующего foreign key. Дело было относительно давно и мб сейчас сделал все это по другому, но тогда flush выручал.

Нативный запрос работает минуя кэш хибера, так что логично что он не видит данных которые лежит в кэше.
Тут нужно или запросы в разных транзакциях выполнять или флашить руками.

Не обязательно руками, есть ещё @Modifying. По сути то же самое, но средствами фреймворка.

saveAndFlush удобен тем, что если при сохранении будет ошибка БД (например, constraint не даст сделать вставку), то исключение вылетит сразу же, и будет видно, на какой строчке кода и на каком объекте это произошло (первом, десятом или сотом). Если сохранять без flush, то исключение будет потом, на последней строчке метода, где находится закрывающая скобка. Попробуй тогда разберись, что случилось. Поэтому везде flush используем, кроме редких случаев, где нужно сохранить больше тысячи объектов за раз.
String concat(String... strings) {
  String result = "";
  for (String str : strings) {
    result += str;
  }
  return result;
}

Разве этот кейс не оптимизируется современным рантаймом?

А голова на что?

Попробуйте побенчмаркать ;) потом расскажете нам

Оптимизируется в StringBuilder c Java 8, вот спецификация , правда анализатор будет ругать как и лид на проекте если такое делать в проде.

Вы правы, хм, я был уверен в обратном.

> И внезапно оказалось, что эта оптимизация работает с конструктором и ломается, если заворачиваемое простое значение протаскивается через кэш внутри Integer::valueOf.

Факт!
В котлиновской стандартной библиотеке есть файл с кучей функций, которые нельзя вызвать извне стандартной библиотеки и они нигде не используются.
github.com/JetBrains/kotlin/blob/master/libraries/stdlib/jvm/src/kotlin/coroutines/jvm/internal/boxing.kt
Эти функции написаны как раз, чтобы заменить valueOf в корутиновом коде. В обычном коде боксинг удаляется компилятором, а в корутиновом приходится примитивы оборачивать. Поэтому компилятор заменяет все вызовы valueOf на вызовы Boxing.box[Type].
в корутиновом приходится примитивы оборачивать

Вроде же котлин отказался от примитивов?

Спасибо, буду знать!

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации