В этом посте предлагаю свои мысли о методе stream()
интерфейса Query
. Этот метод появился в Hibernate не так давно, в версии 5.2, в рамках поддержки новых возможностей Java 8. Что же за ним скрывается, какие особенности его использования и нужен ли он вам? Давайте разберемся.
Сперва предлагаю задаться вопросом: почему бы не воспользоваться методом list()
того же интерфейса и получать объект Stream
уже из него? Так можно сделать, если результат вашей выборки относительно небольшой. Если же вам, например, надо экспортировать многомиллионную таблицу этот вариант плох в плане использования оперативной памяти. До этого вы скорее всего пользовались методом scroll()
в подобных случаях, но он возвращает ScrollableResults
, который не является коллекцией и не содержит метод, возвращающий
Stream
. Теперь вы можете воспользоваться новым методом, чтобы вычитывать данные по мере необходимости.
Что же представляет собой метод stream()
внутри? Это альтернативная реализация метода scroll()
или всего лишь обертка над ним? Не будем долго гадать и заглянем в документацию, в которой явно сказано, что мы имеем дело с оберткой. Как это реализовано технически можно посмотреть в исходном коде. В принципе, ничего сложного и вы легко можете повторить это у себя в коде, если по каким-то причинам вы вынуждены отложить обновление Hibernate до версии 5.2.
Есть ли особенности использования этого метода? Есть и они вряд ли вам понравятся. Давайте вспомним, что объект Query
вы получаете из сессии, которая может иметь состояние (stateful), а может и не иметь его (stateless). Методом stream()
вы можете воспользоваться независимо от того из какого типа сессии получен объект Query
. Однако учтите, что вы теперь не управляете итерацией по результатам выборки, как это было при использовании метода scroll()
, и вам теперь надо найти способ периодически очищать stateful сессию по мере потребления стрима, чтобы разумно использовать оперативную память.
Следует быть внимательным и при передаче объекта, возвращенного методом stream()
, в другой метод. Во-первых, потребление данных возможно до того момента пока не закрылась сессия, а держать ее открытой неопределенно долго не имеет смысла и даже глупо. Во-вторых, вы не защищены от ситуации, когда ваш Stream
захотят распараллелить. А у вас под капотом используется SqlConnection
, который отнюдь не потокобезопасный. Увы, тот другой метод скорее всего ничего не знает об этих подробностях.
Итого, у нас в коде появляется Stream
, который по виду ничем не отличается от остальных, но имеет свои особенности использования. Я бы воспринимал это как потенциальный источник ошибок и поводом задуматься: а может не так уже нам и нужен этот Stream
в качестве результата выборки? Давайте вспомним, что писали о нем в момент выхода Java 8. В “Processing Data with Java SE 8 Streams, Part 1” читаем, что наконец-то появился декларативный способ обрабатывать данные. Там же находим определение stream как последовательности элементов из некоторого источника, поддерживающей агрегирующие операции. А еще ниже прямым текстом написано, что stream — это абстракция, предоставляющая SQL-подобные операции над коллекцией данных. У вас должны быть серьезные основания применять SQL-подобные операции над результатом SQL-запроса (я не исключаю, что такие могут быть).
Использовать Query.stream()
или нет — вам решать. Только будьте начеку, когда он вам встретится в коде. Оградите этот код колючей проволокой. Добавьте комментарий к этому коду, а еще лучше научите ваш статический анализатор громко предупреждать о течи на вашем судне об этом особом потоке.
Если вы не разделяете моего скептицизма прошу в комментарии.
UPD.: В целом, высказанные тут замечания относятся и к возможности, предоставляемой Spring Data JPA, когда тип возвращаемого значения метода репозитория — Stream<T>
. Если в качестве реализации JPA используется Hibernate, то все опять-таки сводится к вызову метода scroll()
(см. метод executeQueryWithResultStream
)
UPD. 2: Stream
добрался и до JPA 2.2. Vlad Mihalcea проанализировал это нововведение у себя в блоге. Должен признаться я упустил действительно важную причину избегать методы,
возвращающие Stream
в качестве результата выборки, — неоптимальный план выполнения запроса.
Комментарии