Einstieg in Spring Boot, Teil 17 Paginieren und Sortieren mit Spring Boot

Von Dr. Dirk Koller

Das Sortieren und Paginieren großer Datenmengen ist auf Webseiten unerlässlich, um Übersicht und Performance zu gewährleisten. Spring Boot hält hierfür spezialisierte Repositories bereit.

Anbieter zum Thema

Sollen viele Datensätze im Web dargestellt werden, empfiehlt es sich, diese zu sortieren und nur häppchenweise auszugeben.
Sollen viele Datensätze im Web dargestellt werden, empfiehlt es sich, diese zu sortieren und nur häppchenweise auszugeben.
(Bild: 200degrees / Pixabay )

Ob tausende Produkte eines Online-Shops oder alle Mitarbeiter eines großen Konzerns: Das Anzeigen von sehr großen Datenmengen auf einer einzigen Webseite ist nicht sehr sinnvoll. Die Seite wird unübersichtlich, schwer navigierbar und braucht lange zum Laden.

Die übliche Vorgehensweise ist es stattdessen, die Daten in gut verarbeitbare Happen (Chunks) zu unterteilen und auf mehreren aufeinanderfolgenden Seiten anzuzeigen. Spring Boot bietet für diesen Anwendungsfall und auch das Sortieren von Daten großartige Repository-Unterstützung.

„Spring Boot“-Tutorial
Bildergalerie mit 22 Bildern

Vorbereitung des Projekts

Als Basis wird hier ein neu angelegtes Spring Boot-Projekt mit den Abhängigkeiten spring-boot-starter-data-jpa, spring-boot-starter-web, spring-boot-devtools, lombok und h2 genutzt. Die Dependencies lassen sich der POM-Datei wahlweise manuell oder über den entsprechenden Wizard (Rechtsklick auf pom.xml > Spring > Edit Starters) in der Spring Tool Suite zufügen.

Als Entität dient die Klasse Person, die schon in anderen Beiträgen dieser Serie zum Einsatz kam. Die Getter und Setter der Klasse werden mit Lombok erzeugt (Annotation @Data):

@Entity
@Data
public class Person {
   @Id
   @GeneratedValue(strategy=GenerationType.AUTO)
   private Long id;
   private String firstname;   private String lastname;
}

Die Personen sollen in der durch die Abhängigkeit eingebundenen H2-Datenbank gespeichert werden. H2 lässt sich über eine Web-Konsole bedienen, die in application.properties unter sr/main/resources aktiviert wird. Zusätzlich wird dort die Datasource-URL für die Datenbank im In-Memory-Modus gesetzt:

spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb

Nach dem Start der Anwendung lässt sich die Konsole unter http://localhost:8080/h2-console im Browser öffnen. Die Tabelle Person wird von Spring Data automatisch angelegt.

Die Testdaten für das Beispiel werden in data.sql unter src/main/resources hinterlegt (am besten auf zehn Datensätze erweitern):

INSERT INTO person VALUES (1, 'Max', 'Mustermann');
INSERT INTO person VALUES (2, 'Ute', 'Musterfrau');

Die Inserts werden beim Start der Anwendung automatisch ausgeführt, unglücklicherweise VOR dem automatischen Anlegen der Tabelle durch Spring Data. Damit die Reihenfolge stimmt und die Tabelle beim Befüllen auch vorhanden ist, ist ein weiterer Eintrag in application.properties erforderlich:

spring.jpa.defer-datasource-initialization=true

Testdaten in der H2-Console.
Testdaten in der H2-Console.
(Bild: Koller / H2)

Die Personen finden sich danach in der Datenbank und die Vorbereitungen sind abgeschlossen.

PagingAndSortingRepository

Für den Zugriff auf die Daten wird ein Spring Data-Repository angelegt, das von PagingAndSortingRepository erbt.

public interface PersonRepository extends PagingAndSortingRepository<Person, Integer> {}

Repository-Vererbungshierarchie
Repository-Vererbungshierarchie
(Bild: Koller / Spring Boot)

Die elegante Möglichkeit, mit Hilfe von Derived Queries auf Datenbankdaten lediglich durch Formulierung von Methodendeklarationen in einem Repository-Interface zuzugreifen, wurde bereits im „Teil 4: Datenbankabfragen mit Spring Boot“ besprochen. Wie das Klassendiagramm zeigt, handelt es sich bei PagingAndSortingRepository um ein CrudRepository, das um Methoden für Pagination und Sortieren von Einträgen erweitert wurde. Ebenfalls aus dem Diagramm ersichtlich ist, dass JpaRepository von PagingAndSortingRepository erbt.

Die nun vorgestellten Möglichkeiten zum Paginieren und Sortieren stehen also auch in diesem Repository zur Verfügung. Die im PagingAndSortingRepository enthaltene findAll()-Methode nimmt ein Pageable-Objekt entgegen, das die Nummer und die Anzahl der Einträge in der Page beschreibt. Im folgenden Beispiel wird die erste Page mit 5 Personen ermittelt:

Pageable firstPage = PageRequest.of(0, 5);
Page<Person> persons = personRepository.findAll(firstPage);

Ähnlich funktioniert das Sortieren, hier lässt sich eine Instanz der Klasse Sort übergeben, die das Attribut enthält, nach dem sortiert werden soll. Die Sortierreihenfolge wird durch Aufruf der Methoden ascending() bzw. descending() auf das Sort-Objekt festgelegt:

Sort sort = Sort.by("firstname").descending();
Iterable<Person> persons = personRepository.findAll(sort);

Mehrere Sortierkriterien können mit der and()-Methode aneinandergehängt werden:

Pageable andSorting = PageRequest.of(0, 5, Sort.by("lastname").descending().and(Sort.by("firsname")));

Paginierung und Sortieren lassen sich selbstverständlich kombinieren:

Sort sort = Sort.by("firstname").ascending();
Pageable secondPageSorted = PageRequest.of(1, 5, sort);
Page<Person> persons = personRepository.findAll(secondPageSorted);

Eigene Methoden im Repository-Interface erhalten den Pageable-Parameter einfach in der Methodensignatur, er wird automatisch ausgewertet:

public interface PersonRepository extends PagingAndSortingRepository<Person, Integer> {
   Page<Person> findAllByLastname(String lastname, Pageable pageable);
}

Der Aufruf birgt dann keine großen Überraschungen:

Pageable pageable = PageRequest.of(1, 2);
Page<Person> persons = personRepository.findAllByLastname("Musterfrau", pageable);

Slices statt Pages

Das Page-Objekt enthält nicht nur die Personen der Page sondern auch Methoden, mit denen sich die Gesamtzahl der vorhandenen Pages und Datensätze herausfinden lässt.

public interface Page<T> extends Slice<T> {
   int getTotalPages();
   long getTotalElements();
   …
}

Wenn diese Daten nicht benötigt werden, sollte man das schlankere Slice-Objekt verwenden, von dem Page erbt:

Pageable pageable = PageRequest.of(1, 2);
Slice<Person> persons = personRepository.findAllByLastname("Musterfrau", pageable);

Da hier kein _select count(*)_ zum Zählen der Datensätze in der Datenbank ausgeführt werden muss, ist die Abfrage schneller. Das Slice-Objekt kennt die Gesamtzahl nicht, weiß aber, ob weitere Slices vor oder nach dem aktuellen Slice existieren:

public interface Slice<T> extends Streamable<T> {
   boolean hasNext();
   boolean hasPrevious();
   …
}

Für viele Anwendungsfälle, beispielsweise eine Webseite mit einfachen Vor/Zurück-Buttons, genügen diese Informationen. Soll dagegen die Anzahl der Pages oder der Datensätze angezeigt werden, wird eine Page benötigt. Die Anbindung des PagingAndSortingRepository an ein User Interface besprechen wir im nächsten Teil der Serie.

„Spring Boot“-Tutorial
Bildergalerie mit 22 Bildern

(ID:47500242)