«1. Обзор

Querydsl — обширная среда Java, помогающая создавать и выполнять запросы с типобезопасностью на предметно-ориентированном языке, похожем на SQL.

В этой статье мы рассмотрим Querydsl с Java Persistence API.

Небольшое замечание: HQL для Hibernate был первым целевым языком для Querydsl, но в настоящее время он поддерживает JPA, JDO, JDBC, Lucene, Hibernate Search, MongoDB, Collections и RDFBean в качестве серверных частей.

2. Подготовка

Давайте сначала добавим необходимые зависимости в наш проект Maven:

<properties>
    <querydsl.version>2.5.0</querydsl.version>
</properties>

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>${querydsl.version}</version>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>${querydsl.version}</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.6.1</version>
</dependency>

А теперь давайте настроим плагин Maven APT:

<project>
    <build>
    <plugins>
    ...
    <plugin>
        <groupId>com.mysema.maven</groupId>
        <artifactId>apt-maven-plugin</artifactId>
        <version>1.1.3</version>
        <executions>
        <execution>
            <goals>
                <goal>process</goal>
            </goals>
            <configuration>
                <outputDirectory>target/generated-sources</outputDirectory>
                <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
            </configuration>
        </execution>
        </executions>
    </plugin>
    ...
    </plugins>
    </build>
</project>

JPAAnnotationProcessor найдет типы доменов, аннотированные с помощью javax .persistence.Entity и генерирует для них типы запросов.

3. Запросы с помощью Querydsl

Запросы создаются на основе сгенерированных типов запросов, которые отражают свойства типов вашего домена. Кроме того, вызовы функций/методов построены полностью типобезопасным способом.

Пути запросов и операции одинаковы во всех реализациях, а также интерфейсы запросов имеют общий базовый интерфейс.

3.1. Сущность и тип запроса Querydsl

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

@Entity
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String firstname;

    @Column
    private String surname;
    
    Person() {
    }

    public Person(String firstname, String surname) {
        this.firstname = firstname;
        this.surname = surname;
    }

    // standard getters and setters

}

Querydsl сгенерирует тип запроса с простым именем QPerson в тот же самый пакет как человек. QPerson можно использовать в качестве статически типизированной переменной в запросах Querydsl в качестве представителя типа Person.

Во-первых, QPerson имеет переменную экземпляра по умолчанию, доступ к которой можно получить как к статическому полю:

QPerson person = QPerson.person;

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

QPerson person = new QPerson("Erich", "Gamma");

3.2. Построение запроса с использованием JPAQuery

Теперь мы можем использовать экземпляры JPAQuery для наших запросов:

JPAQuery query = new JPAQuery(entityManager);

Обратите внимание, что entityManager является JPA EntityManager.

Давайте теперь получим всех людей с именем «Кент» в качестве быстрого примера:

QPerson person = QPerson.person;
List<Person> persons = query.from(person).where(person.firstName.eq("Kent")).list(person);

Вызов from определяет источник запроса и проекцию, часть where определяет фильтр, а список сообщает Querydsl о возврате все совпадающие элементы.

Мы также можем использовать несколько фильтров:

query.from(person).where(person.firstName.eq("Kent"), person.surname.eq("Beck"));

Или:

query.from(person).where(person.firstName.eq("Kent").and(person.surname.eq("Beck")));

В родной форме JPQL запрос будет выглядеть так:

select person from Person as person where person.firstName = "Kent" and person.surname = "Beck"

Если вы хотите объединить фильтры через «или», затем используйте следующий шаблон:

query.from(person).where(person.firstName.eq("Kent").or(person.surname.eq("Beck")));

4. Упорядочивание и агрегирование в Querydsl

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

4.1. Порядок

Начнем с упорядочения результатов в порядке убывания по полю фамилии:

QPerson person = QPerson.person;
List<Person> persons = query.from(person)
    .where(person.firstname.eq(firstname))
    .orderBy(person.surname.desc())
    .list(person);

4.2. Агрегирование

Давайте теперь воспользуемся простым агрегированием, так как у нас есть несколько доступных (Sum, Avg, Max, Min):

QPerson person = QPerson.person;    
int maxAge = query.from(person).list(person.age.max()).get(0);

4.3. Агрегация с помощью GroupBy

Класс com.mysema.query.group.GroupBy предоставляет функциональность агрегации, которую мы можем использовать для агрегирования результатов запроса в памяти.

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

QPerson person = QPerson.person;   
Map<String, Integer> results = 
  query.from(person).transform(
      GroupBy.groupBy(person.firstname).as(GroupBy.max(person.age)));

5. Тестирование с помощью Querydsl

Теперь давайте определим реализацию DAO с помощью Querydsl â – и давайте определим следующую операцию поиска:

public List<Person> findPersonsByFirstnameQuerydsl(String firstname) {
    JPAQuery query = new JPAQuery(em);
    QPerson person = QPerson.person;
    return query.from(person).where(person.firstname.eq(firstname)).list(person);
}

А теперь давайте создадим несколько тестов, используя этот новый DAO, и воспользуемся Querydsl для поиска вновь созданных объектов Person (реализованных в классе PersonDao) и в другом агрегировании тестов, используя Класс GroupBy протестирован:

@Autowired
private PersonDao personDao;

@Test
public void givenExistingPersons_whenFindingPersonByFirstName_thenFound() {
    personDao.save(new Person("Erich", "Gamma"));
    Person person = new Person("Kent", "Beck");
    personDao.save(person);
    personDao.save(new Person("Ralph", "Johnson"));

    Person personFromDb =  personDao.findPersonsByFirstnameQuerydsl("Kent").get(0);
    Assert.assertEquals(person.getId(), personFromDb.getId());
}

@Test
public void givenExistingPersons_whenFindingMaxAgeByName_thenFound() {
    personDao.save(new Person("Kent", "Gamma", 20));
    personDao.save(new Person("Ralph", "Johnson", 35));
    personDao.save(new Person("Kent", "Zivago", 30));

    Map<String, Integer> maxAge = personDao.findMaxAgeByName();
    Assert.assertTrue(maxAge.size() == 2);
    Assert.assertSame(35, maxAge.get("Ralph"));
    Assert.assertSame(30, maxAge.get("Kent"));
}

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

В этом руководстве показано, как создать проект JPA с использованием Querydsl.

Полную реализацию этой статьи можно найти в проекте github — это проект maven на основе Eclipse, поэтому его легко импортировать и запускать как есть.

Небольшое примечание: запустите простую сборку maven (mvn clean install), чтобы сгенерировать типы в target/generated-sources, а затем, если вы используете Eclipse, включите папку как исходная папка проекта.