К основному контенту

java.util.stream.Stream как результат выборки из базы данных

В этом посте предлагаю свои мысли о методе 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 в качестве результата выборки, — неоптимальный план выполнения запроса.


Комментарии

Популярные сообщения из этого блога

Одно приложение, несколько баз данных

Рецепт от Spring Boot Некоторое время назад мне довелось писать агрегатор информации, разбросанной по нескольким базам данных с разными схемами. Для реализации был выбран Spring Boot. Ну, потому что модный и судя по примерам существенно упрощает жизнь за счет умной автоконфигурации. В этой статье я опишу, что же необходимо сконфигурировать и как, в случае, если вы отошли от стандартного сценария. Первым делом, необходимо прописать настройки доступа к каждой из баз. Например, вот так: Следующим шагом создадим отдельный класс конфигурации (для удобства), в котором определим dataSources: Обратите внимание, как просто получить настройки с помощью @ConfigurationProperties. Правда, пришлось ввести вспомогательный класс BaseDataSourceProperties — наследник DataSourceProperties, в котором область видимости метода getDriverClassName расширена до public. И осталось совсем немного — сконфигурировать JPA-репозитории. Насчет немного я, конечно, пошутил :) В этой части предстоит больше ...

Обработка изображений с ImageMagick

ImageMagick ( http://www.imagemagick.org ) — набор утилит для создания, редактирования, конвертирования и просмотра растровых изображений. Графический режим необходим только для просмотра. Для остальных действий над изображениями достаточно консоли. То есть налицо два отличия от привычных редакторов растровых изображений (вроде GIMP или Krita): использование набора утилит вместо одной программы для операций над изображениями не требуется GUI. Очевидно, что таким инструментом вряд ли будут пользоваться художники, фотографы или дизайнеры. Чтобы разобраться для кого предназначен этот набор, предлагаю ознакомиться с предоставляемыми возможностями. Что умеет ImageMagick? Чтобы ответить на поставленный вопрос я перечислю входящие в набор утилиты, напишу какой функционал предоставляет каждая из них и, конечно же, приведу примеры использования. identify — информационная утилита, воспользовавшись которой можно узнать формат изображения и множество других его свойств (например, высоту,...

Изучение Qt маленькими порциями. Разбираем ваше первое приложение

Прошлый раз вы установили QtCreator, а затем сгенерировали ваше первое приложение. Всё сработало и у вас теперь есть собственное пустое окно QMainWindow на экране. Всё хорошо, но что же действительно было сделано? Давайте пройдёмся по сгенерированному коду, чтобы стало понятно, что же случилось на самом деле. Приложение состоит из одного класса и функции main, необходимой для запуска. Давайте посмотрим на эту функцию main.cpp #include <QtGui/QApplication> #include "mainwindow.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } В этой функции создаётся экземпляр класса QApplication . Каждому приложению Qt нужен один и только один экземпляр класса QApplication. Он представляет собой само приложение и содержит главный цикл обработки событий. Вы можете спросить, что такое цикл обработки событий? Это просто цикл, ожидающий наступления события. Например, нажатие клавишы, перемещение мышки, сетевые пакеты, событ...