Как легко реализовать «язык запросов REST API» с помощью Querydsl и Spring Data для фильтрации сущностей? - PullRequest
0 голосов
/ 02 июля 2018

Как легко реализовать своего рода «язык запросов REST API» с помощью Spring Data для фильтрации объектов?

Например, для следующей Person сущности:

@Data
@Entity
public class Person {

  @Id
  @GeneratedValue
  private Long id;

  private LocalDate dob; // date of birth

  private String name;

  @Formula("timestampdiff('year', dob, now())")
  private Integer age;

  public Person(String name, LocalDate dob) {
    this.name = name;
    this.dob = dob;
  }
}

Я бы хотел получить данные с таким запросом:

GET /people?name=jo&age=18&page=1&sort=name,desc

Т.е.: 'получить 1-ю страницу всех людей, чей name содержит "jo" (без учета регистра) и чей age равен 18, сортируя по name в порядке убывания'.

1 Ответ

0 голосов
/ 02 июля 2018

С помощью веб-поддержки Querydsl , входящей в веб-поддержку расширения данных Spring , мы можем легко реализовать своего рода «язык запросов REST API» для фильтрации наших сущностей.

Все, что нам нужно, это сделать следующее:

1) расширить наш репозиторий с QuerydslPredicateExecutor,

2) добавить Predicate с аннотацией @QuerydslPredicate в качестве аргумента к нашему методу контроллера REST

3) использовать этот предикат в findAll методе репозитория:

public interface PersonRepo extends JpaRepository<Person, Long>, QuerydslPredicateExecutor<Person> {
} 
@RequiredArgsConstructor
@RestController
@RequestMapping("/people")
public class PersonController {

    private final PersonRepo personRepo;

    @GetMapping
    public ResponseEntity getFiltered(@QuerydslPredicate(root = Person.class) Predicate predicate, Pageable pageable) {
        return ResponseEntity.ok(personRepo.findAll(predicate, pageable)));
    }
}

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

GET /people?name=John&age=18&page=1&sort=name,desc

Далее мы должны сделать нечувствительный к регистру фильтр «как». Для этого мы расширяем репо с QuerydslBinderCustomizer и переопределяем его метод customize (прямо в репо):

public interface PersonRepo extends
        JpaRepository<Person, Long>,
        QuerydslPredicateExecutor<Person>,
        QuerydslBinderCustomizer<QPerson> {

    @Override
    default void customize(QuerydslBindings bindings, QPerson person) {

        // Make case-insensitive 'like' filter for all string properties 
        bindings.bind(String.class).first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
    }
}

Чтобы это работало, мы должны добавить параметр bindings к @QuerydslPredicate нашего метода контроллера:

@GetMapping
public ResponseEntity getFiltered(
    @QuerydslPredicate(root = Person.class, bindings = PersonRepo.class) Predicate predicate, 
    Pageable pageable
) {
    return ResponseEntity.ok(personRepo.findAll(predicate, pageable)));
}

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

GET /people?name=jo&age=18&page=1&sort=name,desc

С помощью QuerydslBinderCustomizer мы можем реализовать более сложные фильтры, например, фильтры between и greater or equal (добавьте этот код в метод customize):

bindings.bind(person.age).all((path, value) -> {
    Iterator<? extends Integer> it = value.iterator();
    Integer from = it.next();
    if (value.size() >= 2) {
        Integer to = it.next();
        return Optional.of(path.between(from, to)); // between
    } else {
        return Optional.of(path.goe(from)); // greater or equal
    }
});

Если в запросе указать два age параметра, то мы получим все записи с возрастом между этими параметрами. Если мы укажем только один age параметр - мы получим записи, возраст которых больше или равен этому значению.

GET /people?age=18&age=30

... получают все люди в возрасте от 18 до 30

GET /people?age=18

... получить всех людей, возраст которых больше или равен 18

В конце мы можем исключить некоторые ненужные свойства из фильтра, например, сущность id (добавить этот код в метод customize):

bindings.excluding(person.id);

Чтобы использовать веб-поддержку Querydsl, мы должны добавить эти зависимости и плагин в наш проект Spring Boot:

<dependencies>
    <!-- ... -->

    <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-apt</artifactId>
        <scope>provided</scope>
    </dependency>
</dependencies>

<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/annotations</outputDirectory>
                        <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Затем важно, скомпилировать проект , чтобы построить "Q-классы" наших сущностей.

Полный пример демонстрации вы можете найти в моем репозитории: sb-querydsl-sd-demo и Почтальон API-документы этой демонстрации - здесь: Язык запросов REST с данными Querydsl и Spring .

...