JPAstreamer — это библиотека для выражения запросов JPA/Hibernate/Spring с использованием стандартных потоков Java. Она помогает извлекать и обрабатывать записи базы данных таким же образом, как мы обрабатываем POJO.
Например, мы можем извлечь всех неактивных пользователей из базы данных, используя следующий код:
List<User> inactiveUsers = jpaStreamer.stream(User.class).filter(u -> !u.getActive()).collect(Collectors.toList());
Внутри себя JPAstreamer сгенерирует оптимизированный SQL-запрос, который будет запущен в базе данных с использованием источника данных/менеджера сущностей, настроенного в приложении.
Для демонстрации мы будем использовать следующий класс сущности User:
@Data@AllArgsConstructor@NoArgsConstructor@Entitypublic class User {@Id@GeneratedValue(strategy = GenerationType.SEQUENCE)private Long id;private String name;private LocalDate dateOfBirth;private Boolean active;}
1. Начало работы с JPAstreamer
В типичном приложении Spring boot мы можем импортировать и настроить JPAstreamer, импортировав зависимости jpastreamer-core и spring-boot-jpastreamer-autoconfigure.
Зависимость jpastreamer-core транзитивно импортирует все необходимые зависимости и подмодули. Spring-boot-jpastreamer-autoconfigure помогает в автоматической настройке JPAstreamer в приложениях Spring boot, используя его автоконфигурацию, когда у нас уже есть spring-boot-starter-data-jpa в classpath.
<dependency><groupId>com.speedment.jpastreamer</groupId><artifactId>jpastreamer-core</artifactId><version>1.1.4</version></dependency><dependency><groupId>com.speedment.jpastreamer.integration.spring</groupId><artifactId>spring-boot-jpastreamer-autoconfigure</artifactId><version>1.1.4</version></dependency>
Обратите внимание, что в настоящее время JPAstreamer работает с Spring boot 2. Из-за несовместимости с пространством имен 'jakarta.*' он не будет работать с Spring boot 3. Вы можете проверить трекер ошибок на предмет последних обновлений.
2. Инициализация JPAStreamer
В приложении Spring boot автоконфигурация автоматически инициализирует класс JPAStreamer, который предоставляет полностью типобезопасный API для написания запросов JPA. Мы можем автоматически связать его в классе службы или тестовом классе следующим образом:
@Servicepublic class UserService {@AutowiredJPAStreamer jpaStreamer;...}
Мы также можем инициализировать JPAstreamer с именем единицы сохранения. В следующем коде «persistence-unit-name» следует заменить на имя вашей единицы сохранения.
JPAStreamer jpaStreamer = JPAStreamer.of("persistence-unit-name");
За кулисами JPAStreamer автоматически генерирует метамодель процессором аннотаций, который работает во время компиляции, как только JPAstreamer установлен. Процессор аннотаций проверяет все классы, аннотированные @Entity. Например, 'User.class' генерирует эквивалент 'User$.class' в папке '/target' проекта.
Затем во время выполнения используется потоковый рендерер, который преобразует потоковые операции в запросы JPA для повышения производительности.
3. Получение потока из таблицы
Для потоковой передачи сущностей из таблицы мы должны использовать stream(entity-class). Получив поток записей, мы можем обрабатывать записи аналогично стандартным потокам Java.
jpaStreamer.stream(User.class)//other stream operations...
Не забудьте закрыть поток, чтобы освободить все ресурсы, потенциально удерживаемые JPAstreamer.
jpaStreamer.close();
4. Работа с потоками сущностей
Как только у нас есть поток сущностей, мы можем выполнять все виды промежуточных и конечных операций, как мы делаем в стандартных потоках Java. Прочитайте официальную страницу для полного списка поддерживаемых функций.
4.1 Фильтрация с помощью предикатов
В сгенерированном классе метаданных каждое поле создается как ComparableField, и мы можем применять встроенные компараторы для фильтрации элементов потока.
Например, чтобы найти всех пользователей в базе данных, у которых поле dateOfBirth имеет значение null, мы можем использовать следующий оператор:
List<User> usersWithoutDateOfBirth = jpaStreamer.stream(User.class).filter(User$.dateOfBirth.isNull()).collect(Collectors.toList());
В следующей таблице перечислены несколько предикатов для нашего понимания. Вы можете прочитать обо всех поддерживаемых предикатах в официальном руководстве. Обратите внимание, что мы можем создать составной предикат, объединив несколько предикатов с помощью методов and() и or().
Предикат | Описание |
---|---|
isNull, isNotNull | поле равно null или не равно null. |
равно, неравный | поле равно/не равно параметру. |
меньшеИлиРавно, большеИлиРавно, меньшеЧем, большеЧем | поле меньше / больше / равно параметру. |
между, неМежду | поле находится между / не между началом(включительно) и концом(исключая). |
в, неВ | параметр массива содержит/не содержит поле. |
isEmpty, isNotEmpty | строка пустая / не пустая. |
начинается с, не начинается с, начинается сIgnoreCase, не начинается сIgnoreCase | строка начинается с / не начинается с указанного параметра с учетом / без учета регистра. |
endsWith, notEndsWith, endsWithIgnoreCase, notEndsWithIgnoreCase | строка заканчивается на / не заканчивается на указанный параметр с учетом / без учета регистра. |
содержит, не содержит, содержитIgnoreCase, не содержитIgnoreCase | строка содержит/не содержит указанный параметр с учетом/без учета регистра. |
отрицать | отрицает заданный предикат. |
и, или | возвращает составной предикат, представляющий собой замкнутую логическую операцию И/ИЛИ двух заданных предикатов. |
Обратите внимание, что список поддерживаемых предикатов зависит от типа данных поля. Например, isNull() и isNotNull() применимы только для полей непримитивного типа.
4.2 Сортировка
Мы можем использовать sorted() интерфейса Stream и передать необходимый компаратор для сортировки сущностей. Для обратной сортировки используйте компаратор reverse().
jpaStreamer.stream(User.class).sorted(User$.dateOfBirth.reversed())...
4.3.Пагинация
Библиотека также поддерживает функцию пагинации, похожую на простые запросы критериев JPA. Номера страниц начинаются с 0. В следующем операторе мы извлекаем страницу номер 2, тогда как размер страницы равен 10.
int page = 2;int PAGE_SIZE = 10;List<User> users = jpaStreamer.stream(User.class)skip(page * PAGE_SIZE).limit(PAGE_SIZE).toList();
5. Транзакции
Работая с сущностями в потоке, мы также можем обновлять сущности, используя ссылку EntityManager. Необходимо обеспечить, чтобы эти обновления происходили внутри границ транзакции. Мы можем начинать и заканчивать транзакции, используя простые методы JPA следующим образом:
@AutowiredEntityManagerFactory emf;public void updateUsers() {EntityManager em = emf.createEntityManager();JPAStreamer jpaStreamer = JPAStreamer.of(emf);try {em.getTransaction().begin();jpaStreamer.stream(User.class).filter(User$.active.equal(false)).forEach(u -> {u.setActive(true));em.merge(u);}em.getTransaction().commit();} catch(Exception e) {em.getTransaction().rollback();}}
6. Заключение
В этом руководстве по JPAstreamer мы изучили основы библиотеки. Мы научились импортировать и настраивать JPAstreamer в приложении Spring boot, которое уже имеет зависимость spring-boot-starter-data-jpa. Мы также научились создавать и автоматически подключать экземпляр JPAstreamer и передавать потоковые данные из таблицы с помощью различных потоковых операций.