Сортировка и пагинация

Постраничный вывод результатов запроса — весьма популярно требование. И в этом нет ничего удивительного: ведь результатов может быть много, тысячи или сотни тысяч, и их тяжело обрабатывать человеку, который способен сконцентрироваться, в среднем, на семи предметах. Разумеется, в Spring Data Commons есть встроенная поддержка постраничного вывода (пагинации).

Сортировка

Пагинация начинается с сортировки и этому есть причина. Все реляционные базы данных (и не только реляционные, например MongoDB) не гарантируют стабильного порядка строк между выполнением запросов. Например, вы делаете запрос по имени и получаете в ответ следующие строки:

  • Александр
  • Василий
  • Алексей
  • Сергей
  • Пётр

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

  • Сергей
  • Василий
  • Алексей
  • Пётр
  • Александр

Почему это важно для пагинации? Обычно пагинацию делают двумя разными методами:

  • Переповторением запроса с использованием разных OFFSET и LIMIT. В этом случае, так как порядок строк в ответе не определён, база может с чистой совестью возвращать вам одни и те же строки для любых значений LIMIT и OFFSET просто потому что ей так удобнее, данные эти и так уже в памяти и вообще, возвращаю что хочу, законом не запрещено.
  • Открытие курсора и и перемещение по нему. С одной стороны этот метод более идеологически правилен, так как создаёт меньшую нагрузку на базу (меньше запросов) и гарантирует стабильность данных,  с другой стороны, он сложнее в реализации и сильно удлиняет транзакции. К тому же, если и здесь обойтись без сортировки, то при каждом запросе вы будете искать, где же та строчка, с которой я только что работал.

В Spring Data Commons есть поддержка статической сортировки, которая описывается прямо в имени метода: Iterable findByFirstNameOrderByLastNameAsc(String firstName), недостатки у которой как у всего статического: если во время исполнения надо сортировать по разному, то либо пишете несколько методов, либо используете динамическую сортировку.

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

Collection<Person> findByFirstName(String name, Sort sort);
Sort firstnameSort = new Sort(Sort.Direction.Desc, "firstName")
Sort lastNameDOBSort = new Sort(Sort.Direction.Asc, "lastName", "DOB")
Sort allNamesSort = new Sort(new Sort.Order(Sort.Direction.Desc, "firstName"), new Sort.Order(Sort.Direction.Asc, "lastName"))

Класс Sort имеет множество конструкторов, из которых выше показаны два наиболее употребимых:

  • Sort(Sort.Direction direction, String... properties) — создаёт объект сортировки в заданном направлении по указанным полям, одному или нескольким.
  • Sort(Sort.Order... orders) — создаёт объект сортировки из нескольких объектов Sort.Order, которые создаются с указанием направления сортировки и поля сортировки. Это позволяет, в отличие от предыдущего конструктора, делать разнонаправленную сортировку по разным полям.

Наконец, объект Sort передаётся в метод запроса и результат будет автоматически отсортирован в соответствии с содержимым объекта.

Пагинация

Пагинация сделана похожим образом: вы описываете как сформировать страницу и передаёте описание в запрос. Описание страницы инкапсулирует в себя класс PageRequest, у которого три конструктора:

  • PageRequest(int page, int size) — указывается, какую страницу надо вернуть и какой размер страницы в строках. Учтите, что нумерация страниц начинается с нуля. Этот метод используется для запросов со статической сортировкой и с запросами, в которые объект Sort передаётся явно.
  • PageRequest(int page, int size, Sort sort) — всё тоже самое и дополнительно объект сортировки, задающий стабильный порядок строк
  • PageRequest(int page, int size, Sort.Direction direction, String... properties) — как и предыдущий, с той разницей, что сортировка задаётся явно, без использования промежуточного объекта

Объект PageRequest (на самом деле реализация интерфейса Pageable) передаётся в запрос, который автоматически начинает возвращать данные только запрошенной страницы:

Collection<Person> findByFirstName(String name, Pageable page);
Pageable page = new PageRequest(0, 10, SortDirection.Asc, "firstName")
repository.findByFirstName("Test", page)

 

Объект PageRequest имеет так же вспомогательные методы, упрощающие перемещение по страницам. Часть этих методов унаследована от интерфейса Pageable:

  • previous() и next() — возвращают Pageable указывающий на предыдущую/следующую страницу
  • first() и previousOrFirst() — возвращают Pageable, указывающий на первую страницу. previousOrFirst() вернёт Pageable указывающий на предыдущую страницу или на первую, если текущая страница первая или вторая
  • getSort() — возвращает объект сортировки, используемый в PageRequest
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *