Comments 21
Получается, что методJpaRepository::saveAndFlush
даёт нам доступ к нижним уровням API, хотя задача фреймворка как раз в сокрытии низкоуровневых подробностей (ситуация чем-то похожа на историю сUnsafe
в JDK).
Всё же иногда возникают ситуации когда действительно нужно принудительно сбросить кэш операций в БД и этот момент упрощает работу в таком случае.
Согласен, видел и такое. Нередко этим лечились некоторые ошибки в самом Хибернейте или неправильный маппинг с нашей стороны. Как правило, происходило примерно следующее:
- тестировщики завели баг
- при отладке оказалось, что данные, которые мы ожидаем в базе на определённом шаге сложного сценария, в БД не появляются
- сделали флаш и всё заработало.
Это рабочая, но всё же немного костыльная схема: правильное решение, ИМХО, это правильный маппинг или исправление в самом Хибернейте.
В крупном и сложном проекте спустя несколько лет после того, как конкретный флаш был прописан в коде, очень сложно сказать действительно ли он там нужен (и без него ну никак), или это костыль, которым на скорую руку закрыли ошибку на юате.
Для того чтобы проверить нужен flush
или нет — есть тесты.
Если он не нужен, то тесты после его убираения не упадут.
Если тесты, конечно, вообще есть и есть тест который проверяет конкретную ситуацию в которой flush
был необходим.
А если flush используется как синхронизатор (гарантировать, что не будет гонки между записью в базу и последующим кодом) и баг в его отсутствие "плавающий"? Или на этот случай есть варианты лучше?
Тут к с любым плавающим багом — нужно разбираться в чём его причина.
Но тут проблема в том что это занимает больше времени и в случае когда баг редок и/или не критичен, то проще забить.
Ну или использовать костыль
, который может бить по производительности всё время, но в рельутате это всё равно будет дешевле прямого решения проблемы.
Если у вас уже есть понимание что бага происходит из-за состояния гонки, то нужно копать дальше — что приводит к состоянию гонки.
Может проблема в порядке операций/запросов. Может в кеше хибера. Может в чём-то ещё.
Для того чтобы решить нужен ли flush
вообще и можно ли его убрать или заменить на что-то другое — нужно разобраться в первопричине ошибки. А для этого для начала сделать её не "плавающей", а "воспроизводимой". И потом уже смотреть на причины. И уже в зависимости от точно выявленных причин можно начинать решать костыль
ли flush
или нет.
Ниже рассмотрен вариант когда нативный запрос не видит данные которые "вставлялись" через Hibernate, и он их не увидит потому что Hibernate ещё не сбросил свой кэш операций этих записей в БД и там их ещё реально ещё нет.
В этому случае flush
это уже не такой уж и костыль
, особенно если работать точно нужно в одной транзакции (а чаще всего так и нужно).
Хотя тут можно попробовать организовать вложенные транзакции и вставку делать в более глубокой транзакции, по отношению к той где будет использован нативный запрос. Будет ли это быстрее (по работе) и проще (в реализации) — тот ещё вопрос.
На проекте, в котором выловил этот пример, интеграционными тестами был покрыт только ядерный функционал (специфика проекта), в который именно этот флаш не попал. Конечно, было бы гораздо проще с полным интеграционным покрытием.
saveAndFlush
удобен тем, что если при сохранении будет ошибка БД (например, constraint не даст сделать вставку), то исключение вылетит сразу же, и будет видно, на какой строчке кода и на каком объекте это произошло (первом, десятом или сотом). Если сохранять без flush, то исключение будет потом, на последней строчке метода, где находится закрывающая скобка. Попробуй тогда разберись, что случилось. Поэтому везде flush используем, кроме редких случаев, где нужно сохранить больше тысячи объектов за раз.String concat(String... strings) {
String result = "";
for (String str : strings) {
result += str;
}
return result;
}
Разве этот кейс не оптимизируется современным рантаймом?
Факт!
В котлиновской стандартной библиотеке есть файл с кучей функций, которые нельзя вызвать извне стандартной библиотеки и они нигде не используются.
github.com/JetBrains/kotlin/blob/master/libraries/stdlib/jvm/src/kotlin/coroutines/jvm/internal/boxing.kt
Эти функции написаны как раз, чтобы заменить valueOf в корутиновом коде. В обычном коде боксинг удаляется компилятором, а в корутиновом приходится примитивы оборачивать. Поэтому компилятор заменяет все вызовы valueOf на вызовы Boxing.box[Type].
в корутиновом приходится примитивы оборачивать
Вроде же котлин отказался от примитивов?
На уровне языка да, на уровне байт-кода нет.
https://kotlinlang.org/docs/reference/basic-types.html
Обертки используются только тогда когда это необходимо.
Мины под производительностью ждут своего часа