Einstieg in Spring Boot, Teil 18 Paging- und Sorting-Repository mit Thymeleaf

Autor / Redakteur: Dr. Dirk Koller / Stephan Augsten

Das PagingAndSorting-Repository von Spring, mit dessen Hilfe sich Datenbankabfragen in Pages unterteilen lassen, haben wir uns im vorangegangenen Beitrag angesehen. Die Anbindung des Repositories an eine Webseite ist Inhalt dieses Artikels.

Firma zum Thema

Das Sortieren und Paginieren ist bei größeren Datenmengen nahezu unerlässlich.
Das Sortieren und Paginieren ist bei größeren Datenmengen nahezu unerlässlich.
(© Sikov - stock.adobe.com)

Eine der häufigsten Anwendungen für das Paging sind Webseiten mit Next/Previous-Buttons. Für unseren Anwendungsfall setzen wir das entsprechende Projekt aus dem vorangegangenen Beitrag „Paginieren und Sortieren mit Spring Boot“ voraus – mit der Entität Person, dem PersonRepository sowie einigen Testpersonen in der H2-Datenbank.

„Spring Boot“-Tutorial
Bildergalerie mit 18 Bildern

Aufbauend darauf wird zunächst die Projektkonfiguration in pom.xml um eine Abhängigkeit zu Thymeleaf ergänzt:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Wie immer lässt sich dieser Schritt alternativ in der Spring Tool Suite mit dem Assistenten (Rechtsklick auf pom.xml > Spring > Edit Starters) durchführen.

Thymeleaf ist eine serverseitige Template-Engine, die bereits im zweiten Teil „Views mit Thymeleaf“ vorgestellt wurde. Sie soll hier als View-Technologie eingesetzt werden. Wer noch keine Erfahrung mit Thymeleaf hat, sollte sich vielleicht zuvor mit den Grundlagen vertraut machen.

Datenbeschaffung im Controller

Der anzulegende Controller PersonController bekommt das PagingAndSorting-Repository namens PersonRepository mittels @Autowired injiziert und bedient eingehende GET-Anfragen auf den Pfad /persons in der Methode listPersons(). Als Parameter können Größe und Nummer der gewünschten Page übergeben werden. Fehlen diese Angaben, wird mit Hilfe der Default-Einträge die Chunk-Size auf fünf Einträge begrenzt und die erste Page zurückgegeben.

Im Aufruf der Repository-Methode wird die Nummer der gewünschten Page um einen Zähler reduziert, weil dort die Nummerierung bei 0 beginnt. Die zurückerhaltene Page mit den Daten wird als Attribut in das View-Model gepackt und somit der Thymeleaf-View persons.html übergeben.

Neben den Personen erhält die View außerdem eine Liste der vorhandenen Page-Nummern. Die Gesamtzahl der Seiten bei der übergebenen Page-Size ist über die Methode getTotalPages() des Page-Objets zugänglich. Im if-Statement werden die Page-Nummern aufbereitet.

Zu guter Letzt wird der String persons als Rückgabewert der Methode an den Aufrufer zurückgegeben. Das führt bei eingebundener Thymeleaf-Dependency defaultmäßig zur Anzeige der Datei persons.html unter src/main/resources/templates:

@Controller
public class PersonController {
   @Autowired
   PersonRepository personRepository;
   @GetMapping(value = "/persons")
   public String listPersons(
      Model model,
      @RequestParam(defaultValue = "1") Integer page,
      @RequestParam(defaultValue = "5") Integer size) {
         Page<Person> personPage = personRepository.findAll(PageRequest.of(page - 1, size));
         model.addAttribute("personPage", personPage);
         int totalPages = personPage.getTotalPages();
         if (totalPages > 0) {
            List<Integer> pageNumbers = IntStream.rangeClosed(1, totalPages)
               .boxed()
               .collect(Collectors.toList());
            model.addAttribute("pageNumbers", pageNumbers);
         }
         return "persons";
   }
}

In größeren Anwendungen würde man eine Service-Klasse zwischen Controller und Repository schalten, hier im Beitrag soll der Code schlank gehalten werden.

Die Thymeleaf-View

Die Thymeleaf-View ist im nächsten Listing wiedergegeben:

<html>
<head>
<style>
.active {
   border: 1px solid black;
   margin: 2px;
   padding: 2px;
}
</style>
</head>
<body>
   <table>
      <tbody>
         <tr th:each="person : ${personPage.content}">
            <td th:text="${person.id}" />
            <td th:text="${person.firstname}" />
            <td th:text="${person.lastname}" />
         </tr>
      </tbody>
   </table>
   <!-- Previous -->
   <span th:if="${personPage.hasPrevious()}"> <a
         th:href="@{/persons(size=${personPage.size}, page=${personPage.number+1-1})}"
         th:text="Previous"></a>
   </span>
   <!-- Page-Nummern -->
   <span th:if="${personPage.totalPages > 0}"
         th:each="pageNumber : ${pageNumbers}">
      <a th:href="@{/persons(size=${personPage.size}, page=${pageNumber})}"
         th:text=${pageNumber}
         th:class="${pageNumber==personPage.number + 1} ? active"></a>
   </span>
   <!-- Next -->
   <span th:if="${personPage.hasNext()}"> <a
         th:href="@{/persons(size=${personPage.size}, page=${personPage.number+1+1})}"
         th:text="Next"></a>
   </span>
</body>

In der HTML-Tabelle wird über die im Model übergebenen Personen (personPage.content) iteriert und diese als Tabellenzeilen angezeigt. Die darauf folgenden Span-Tags enthalten den Previous-Link, die Liste der Page-Nummern und den Next-Link. Beim Previous- und Next-Link helfen die Methoden hasPrevious() und hasNext() zu entscheiden, ob die Links sichtbar sein sollen.

Die Rechnerei (personPage.number+1-1) im Ausdruck für den Page-Parameter hat wieder mit der bei 0 beginnenden ersten Page zu tun und wurde des Verständnisses wegen nicht gleich zusammengefasst. Die erste Operation (+1) korrigiert den unterschiedlichen Beginn des Index, die zweite Operation (-1 bei Previous bzw. +1 bei Next) wechselt zur vorherigen oder nachfolgenden Seite.

Im mittleren Span-Tag wird über die dem Model übergebenen Page-Nummern iteriert, sofern diese vorhanden sind. Die Nummern werden anschließend in Form von Links angezeigt und der aktuelle Link (pageNumber==personPage.number + 1) mit der eingangs definierten CSS-Klasse active versehen.

„Spring Boot“-Tutorial
Bildergalerie mit 18 Bildern

Aufgerufen wird der Controller entweder mit http://localhost:8080/persons oder zum Beispiel mit http://localhost:8080/persons?size=5&page=2, wenn die Defaultwerte nicht verwendet werden sollen.

Paging-Navigationselemente
Paging-Navigationselemente
(Bild: Koller)

Die Abbildung zeigt die resultierende Tabelle. Page 2 mit fünf der insgesamt 10 Einträge ist grade aktiv und entsprechend gekennzeichnet. Der Next-Button ist ausgeblendet, weil keine weiteren Einträge existieren.

Mit dem PagingAndSortingRespository bietet Spring eine mächtige Unterstützung für den gezeigten Anwendungsfall. Aber auch ohne Thymeleaf-UI, beispielsweise in REST-APIs lässt sich das Repository und die zugehörigen Klassen hervorragend einsetzen.

(ID:47526776)

Über den Autor