Рубрики
Без рубрики

Введение в Querydsl

Простое и практичное руководство по Querydsl.

Автор оригинала: baeldung.

1. введение

Это вводная статья, которая поможет вам начать работу с мощным Querydsl API для сохранения данных.

Цель здесь состоит в том, чтобы дать вам практические инструменты для добавления Querydsl в ваш проект, понять структуру и назначение созданных классов, а также получить базовое представление о том, как писать типобезопасные запросы к базе данных для наиболее распространенных сценариев.

2. Цель Querydsl

Фреймворки объектно-реляционного отображения лежат в основе корпоративной Java. Они компенсируют несоответствие между объектно-ориентированным подходом и реляционной моделью базы данных. Они также позволяют разработчикам писать более чистый и лаконичный код сохранения и логику домена.

Однако одним из самых сложных вариантов проектирования для платформы ORM является API для построения правильных и типобезопасных запросов.

Один из наиболее широко используемых фреймворков Java ORM, Hibernate (а также тесно связанный стандарт JPA), предлагает строковый язык запросов HQL (JPQL), очень похожий на SQL. Очевидными недостатками этого подхода являются отсутствие безопасности типов и отсутствие статической проверки запросов. Кроме того, в более сложных случаях (например, когда запрос должен быть создан во время выполнения в зависимости от некоторых условий) построение запроса HQL обычно включает в себя объединение строк, что обычно очень небезопасно и подвержено ошибкам.

Стандарт JPA 2.0 принес улучшение в виде Criteries Query API — нового и типобезопасного метода построения запросов, который использовал преимущества классов метамоделей, созданных во время предварительной обработки аннотаций. К сожалению, будучи новаторским по своей сути, API запросов критериев оказался очень многословным и практически нечитаемым. Вот пример из учебника по Jakarta EE для создания такого простого запроса, как SELECT p FROM Pet p :

EntityManager em = ...;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(Pet.class);
Root pet = cq.from(Pet.class);
cq.select(pet);
TypedQuery q = em.createQuery(cq);
List allPets = q.getResultList();

Неудивительно, что вскоре появилась более адекватная библиотека Querydsl , основанная на той же идее сгенерированных классов метаданных, но реализованная с помощью свободного и читаемого API.

3. Генерация класса Querydsl

Давайте начнем с создания и изучения магических метаклассов, которые объясняют свободный API Querydsl.

3.1. Добавление Querydsl в сборку Maven

Включение Querydsl в ваш проект так же просто, как добавление нескольких зависимостей в файл сборки и настройка плагина для обработки аннотаций JPA. Давайте начнем с зависимостей. Версия библиотек Querydsl должна быть извлечена в отдельное свойство в разделе следующим образом (для последней версии библиотек Querydsl проверьте Maven Central репозиторий):


    4.1.3

Затем добавьте следующие зависимости в раздел <проект><зависимости> вашего pom.xml файл:



    
        com.querydsl
        querydsl-apt
        ${querydsl.version}
        provided
    

    
        com.querydsl
        querydsl-jpa
        ${querydsl.version}
    

Зависимость querydsl-apt – это инструмент обработки аннотаций (APT) — реализация соответствующего Java API, которая позволяет обрабатывать аннотации в исходных файлах, прежде чем они перейдут на стадию компиляции. Этот инструмент генерирует так называемые Q-типы — классы, которые непосредственно относятся к классам сущностей вашего приложения, но имеют префикс буквы Q. Например, если в вашем приложении есть класс User , помеченный аннотацией @Entity , то сгенерированный Q-тип будет находиться в QUser.java исходный файл.

предоставленная область зависимости querydsl-apt означает, что этот jar должен быть доступен только во время сборки, но не включен в артефакт приложения.

Библиотека querydsl-jpa-это сам Querydsl, предназначенный для использования вместе с приложением JPA.

Чтобы настроить плагин обработки аннотаций , использующий преимущества querydsl-apt , добавьте следующую конфигурацию плагина в свой pom – внутри элемента :


    com.mysema.maven
    apt-maven-plugin
    1.1.3
    
        
            
                process
            
            
                target/generated-sources/java
                com.querydsl.apt.jpa.JPAAnnotationProcessor
            
        
    

Этот плагин гарантирует, что Q-типы генерируются во время цели процесса сборки Maven. Свойство outputDirectory configuration указывает на каталог, в котором будут созданы исходные файлы Q-типа. Значение этого свойства будет полезно позже, когда вы перейдете к изучению Q-файлов.

Вы также должны добавить этот каталог в исходные папки проекта, если ваша IDE не делает этого автоматически — обратитесь к документации для вашей любимой IDE о том, как это сделать.

Для этой статьи мы будем использовать простую модель JPA сервиса блога, состоящую из Пользователей и их Сообщений в блоге с отношением “один ко многим” между ними:

@Entity
public class User {

    @Id
    @GeneratedValue
    private Long id;

    private String login;

    private Boolean disabled;

    @OneToMany(cascade = CascadeType.PERSIST, mappedBy = "user")
    private Set blogPosts = new HashSet<>(0);

    // getters and setters

}

@Entity
public class BlogPost {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String body;

    @ManyToOne
    private User user;

    // getters and setters

}

Чтобы создать Q-типы для вашей модели, просто запустите:

mvn compile

3.2. Изучение Сгенерированных Классов

Теперь перейдите в каталог, указанный в свойстве output Directory apt-maven-plugin ( target/generated-sources/java в нашем примере). Вы увидите структуру пакетов и классов, которая непосредственно отражает вашу модель домена, за исключением того, что все классы начинаются с буквы Q ( QUser и QBlogPost в нашем случае).

Откройте файл QUser.java . Это ваша точка входа для построения всех запросов, которые имеют User в качестве корневой сущности. Первое, что вы заметите, – это аннотацию @Generated , которая означает, что этот файл был создан автоматически и не должен редактироваться вручную. Если вы измените какой-либо из классов модели домена, вам придется снова запустить mvn compile , чтобы восстановить все соответствующие Q-типы.

Помимо нескольких конструкторов QUser , присутствующих в этом файле, вы также должны обратить внимание на открытый статический конечный экземпляр класса QUser :

public static final QUser user = new QUser("user");

Это экземпляр, который вы можете использовать в большинстве запросов Querydsl к этой сущности, за исключением случаев, когда вам нужно написать несколько более сложных запросов, таких как объединение нескольких различных экземпляров таблицы в одном запросе.

Последнее, что следует отметить, это то, что для каждого поля класса сущностей есть соответствующее поле *Path в Q-типе, например NumberPath id , StringPath login и setPath blogPosts в классе QUser (обратите внимание, что имя поля, соответствующего Set , является множественным). Эти поля используются как части API fluent query, с которыми мы столкнемся позже.

4. Выполнение Запросов С Помощью Querydsl

4.1. Простой запрос и фильтрация

Чтобы построить запрос, сначала нам понадобится экземпляр JPAQueryFactory , который является предпочтительным способом запуска процесса построения. Единственное , что нужно JPAQueryFactory , – это EntityManager , который уже должен быть доступен в вашем приложении JPA через EntityManagerFactory.createEntityManager() вызов или @PersistenceContext инъекция.

EntityManagerFactory emf = 
  Persistence.createEntityManagerFactory("com.baeldung.querydsl.intro");
EntityManager em = entityManagerFactory.createEntityManager();
JPAQueryFactory queryFactory = new JPAQueryFactory(em);

Теперь давайте создадим наш первый запрос:

QUser user = QUser.user;

User c = queryFactory.selectFrom(user)
  .where(user.login.eq("David"))
  .fetchOne();

Обратите внимание, что мы определили локальную переменную Kuser user и инициализировали ее с помощью статического экземпляра User.user . Это делается исключительно для краткости, в качестве альтернативы вы можете импортировать статическое поле User.user .

Метод selectFrom из JPAQueryFactory начинает построение запроса. Мы передаем ему экземпляр QUser и продолжаем создавать условное предложение запроса с помощью метода .where () . user.login – это ссылка на поле String Path класса User , которое мы видели ранее. Объект String Path также имеет метод .eq () , который позволяет свободно продолжить построение запроса, указав условие равенства полей.

Наконец, чтобы извлечь значение из базы данных в контекст сохранения, мы завершаем цепочку построения вызовом метода fetchOne () . Этот метод возвращает null , если объект не может быть найден, но вызывает исключение NonUniqueResultException , если существует несколько сущностей, удовлетворяющих условию .where () .

4.2. Упорядочение и группировка

Теперь давайте выберем всех пользователей в списке, отсортированном по их логике в порядке восхождения.

List c = queryFactory.selectFrom(user)
  .orderBy(user.login.asc())
  .fetch();

Этот синтаксис возможен, поскольку классы *Path имеют методы .asc() и .desc () . Вы также можете указать несколько аргументов для метода .OrderBy() для сортировки по нескольким полям.

Теперь давайте попробуем что-нибудь более сложное. Предположим, нам нужно сгруппировать все сообщения по названиям и подсчитать дублирующие заголовки. Это делается с помощью предложения .group By () . Мы также хотим упорядочить заголовки по результирующему количеству вхождений.

NumberPath count = Expressions.numberPath(Long.class, "c");

List userTitleCounts = queryFactory.select(
  blogPost.title, blogPost.id.count().as(count))
  .from(blogPost)
  .groupBy(blogPost.title)
  .orderBy(count.desc())
  .fetch();

Мы выбрали заголовок сообщения в блоге и количество дубликатов, сгруппировав их по названию, а затем упорядочив по агрегированному количеству. Обратите внимание, что мы сначала создали псевдоним для поля count() в поле . select() предложение, потому что нам нужно было ссылаться на него в предложении .OrderBy () .

4.3. Сложные запросы С Объединениями и Подзапросами

Давайте найдем всех пользователей, которые написали пост под названием “Привет, мир!” Для такого запроса мы могли бы использовать внутреннее соединение. Обратите внимание, что мы создали псевдоним запись в блоге для объединенной таблицы, чтобы ссылаться на нее в предложении .on() :

QBlogPost blogPost = QBlogPost.blogPost;

List users = queryFactory.selectFrom(user)
  .innerJoin(user.blogPosts, blogPost)
  .on(blogPost.title.eq("Hello World!"))
  .fetch();

Теперь давайте попробуем добиться того же с подзапросом:

List users = queryFactory.selectFrom(user)
  .where(user.id.in(
    JPAExpressions.select(blogPost.user.id)
      .from(blogPost)
      .where(blogPost.title.eq("Hello World!"))))
  .fetch();

Как мы видим, подзапросы очень похожи на запросы, и они также вполне читаемы, но они начинаются с JPAExpressions заводских методов. Чтобы связать подзапросы с основным запросом, как всегда, мы ссылаемся на псевдонимы, определенные и использованные ранее.

4.4. Изменение Данных

JPAQueryFactory позволяет не только создавать запросы, но и изменять и удалять записи. Давайте изменим логин пользователя и отключим учетную запись:

queryFactory.update(user)
  .where(user.login.eq("Ash"))
  .set(user.login, "Ash2")
  .set(user.disabled, true)
  .execute();

У нас может быть любое количество предложений .set () , которые мы хотим для разных полей. Предложение .where() не является необходимым, поэтому мы можем обновить все записи сразу.

Чтобы удалить записи, соответствующие определенному условию, мы можем использовать аналогичный синтаксис:

queryFactory.delete(user)
  .where(user.login.eq("David"))
  .execute();

Предложение .where() также не является необходимым, но будьте осторожны, потому что пропуск предложения .where() приводит к удалению всех сущностей определенного типа.

Вы можете задаться вопросом, почему JPAQueryFactory не имеет метода .insert () . Это ограничение интерфейса запросов JPA. Лежащая в основе javax.persistence.Метод Query.executeUpdate() способен выполнять инструкции update и delete, но не insert. Чтобы вставить данные, вы должны просто сохранить объекты в EntityManager.

Если вы все еще хотите использовать аналогичный синтаксис Querydsl для вставки данных, вам следует использовать класс SQLQueryFactory , который находится в библиотеке querydsl-sql.

5. Заключение

В этой статье мы обнаружили мощный и типобезопасный API для постоянных манипуляций с объектами, предоставляемый Querydsl.

Мы научились добавлять Querydsl в проект и изучили сгенерированные Q-типы. Мы также рассмотрели некоторые типичные случаи использования и наслаждались их краткостью и удобочитаемостью.

Весь исходный код примеров можно найти в репозитории github .

Наконец, есть, конечно, еще много функций, которые предоставляет Querydsl, включая работу с необработанным SQL, непостоянными коллекциями, базами данных NoSQL и полнотекстовым поиском-и мы рассмотрим некоторые из них в будущих статьях.