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

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

Может ли TreeSet содержать дубликаты?

Не спешите давать ответ, а лучше запустите код, приведенный ниже. Возможно вы подумаете, что это надуманный пример, но это лишь немного упрощенная версия того кода, с которым мне пришлось столкнуться на практике. Для начала о проблеме, которую был призван решить этот код. В списке объектов надо было оставить объекты с различными именами, причем неважно какой объект из всего набора с таким же именем останется. Сразу же уточню, что на практике в проекте используется Java 7 и класс объектов, добавляемых в TreeSet, не вложен в какой-либо другой, а приведенный здесь вариант использован для краткости. Может возникнуть вопрос почему выбрано именно такое решение. Давайте разберемся, чтобы не повторять ошибку. Первое, что приходит в голову, когда из списка надо удалить дубликаты, - использовать Set. В данной ситуации HashSet не подходит, потому что он использует equals метод для сравнения и отсеивания дубликатов. Но наш программист не сдаётся и вспоминает, что есть же еще TreeSet, в докум

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

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