Paginierung mit Spring REST- und AngularJS-Tabelle

REST Top

Ich habe gerade den neuen Learn Spring- Kurs angekündigt , der sich auf die Grundlagen von Spring 5 und Spring Boot 2 konzentriert:

>> Überprüfen Sie den Kurs

1. Übersicht

In diesem Artikel konzentrieren wir uns hauptsächlich auf die Implementierung der serverseitigen Paginierung in einer Spring REST-API und einem einfachen AngularJS-Frontend.

Wir werden auch ein häufig verwendetes Tabellenraster in Angular mit dem Namen UI-Raster untersuchen.

2. Abhängigkeiten

Hier beschreiben wir verschiedene Abhängigkeiten, die für diesen Artikel erforderlich sind.

2.1. JavaScript

Damit Angular UI Grid funktioniert, müssen die folgenden Skripte in unser HTML importiert werden.

  • Angular JS (1.5.8)
  • Angular UI Grid

2.2. Maven

Für unser Backend verwenden wir Spring Boot , daher benötigen wir die folgenden Abhängigkeiten:

 org.springframework.boot spring-boot-starter-web   org.springframework.boot spring-boot-starter-tomcat provided 

Hinweis: Andere Abhängigkeiten wurden hier nicht angegeben. Die vollständige Liste finden Sie in der vollständigen pom.xml im GitHub-Projekt.

3. Über die Anwendung

Die Anwendung ist eine einfache Schülerverzeichnis-App, mit der Benutzer die Schülerdetails in einem paginierten Tabellenraster anzeigen können.

Die Anwendung verwendet Spring Boot und wird auf einem eingebetteten Tomcat-Server mit einer eingebetteten Datenbank ausgeführt.

Auf der API-Seite gibt es schließlich einige Möglichkeiten zur Paginierung, die im Artikel REST-Paginierung im Frühjahr hier beschrieben werden. Dies wird in Verbindung mit diesem Artikel dringend empfohlen.

Unsere Lösung hier ist einfach: Die Paging-Informationen in einer URI-Abfrage lauten wie folgt: / student / get? Page = 1 & size = 2 .

4. Die Client-Seite

Zuerst müssen wir die clientseitige Logik erstellen.

4.1. Das UI-Grid

Unsere index.html enthält die erforderlichen Importe und eine einfache Implementierung des Tabellenrasters:

Schauen wir uns den Code genauer an:

  • ng-app - ist die Angular-Direktive, die die Modul- App lädt . Alle Elemente unter diesen werden Teil des App- Moduls
  • ng-controller - ist die Angular-Direktive, die den Controller StudentCtrl mit einem Alias ​​von vm lädt. Alle Elemente unter diesen sind Teil des StudentCtrl- Controllers
  • ui-grid - ist die Angular-Direktive, die zu Angular ui-grid gehört und gridOptions als Standardeinstellungen verwendet. gridOptions wird in app.js unter $ scope deklariert

4.2. Das AngularJS-Modul

Definieren wir zunächst das Modul in app.js :

var app = angular.module('app', ['ui.grid','ui.grid.pagination']);

Wir haben das App- Modul deklariert und ui.grid eingefügt , um die UI-Grid-Funktionalität zu aktivieren. Wir haben auch ui.grid.pagination injiziert , um die Unterstützung der Paginierung zu ermöglichen.

Als nächstes definieren wir den Controller:

app.controller('StudentCtrl', ['$scope','StudentService', function ($scope, StudentService) { var paginationOptions = { pageNumber: 1, pageSize: 5, sort: null }; StudentService.getStudents( paginationOptions.pageNumber, paginationOptions.pageSize).success(function(data){ $scope.gridOptions.data = data.content; $scope.gridOptions.totalItems = data.totalElements; }); $scope.gridOptions = { paginationPageSizes: [5, 10, 20], paginationPageSize: paginationOptions.pageSize, enableColumnMenus:false, useExternalPagination: true, columnDefs: [ { name: 'id' }, { name: 'name' }, { name: 'gender' }, { name: 'age' } ], onRegisterApi: function(gridApi) { $scope.gridApi = gridApi; gridApi.pagination.on.paginationChanged( $scope, function (newPage, pageSize) { paginationOptions.pageNumber = newPage; paginationOptions.pageSize = pageSize; StudentService.getStudents(newPage,pageSize) .success(function(data){ $scope.gridOptions.data = data.content; $scope.gridOptions.totalItems = data.totalElements; }); }); } }; }]); 

Schauen wir uns nun die benutzerdefinierten Paginierungseinstellungen in $ scope.gridOptions an :

  • paginationPageSizes - Definiert die verfügbaren Optionen für die Seitengröße
  • paginationPageSize - Definiert die Standardseitengröße
  • enableColumnMenus - wird verwendet, um das Menü für Spalten zu aktivieren / deaktivieren
  • useExternalPagination - ist erforderlich, wenn Sie auf der Serverseite paginieren
  • columnDefs - Die Spaltennamen, die automatisch dem vom Server zurückgegebenen JSON-Objekt zugeordnet werden. Die vom Server zurückgegebenen Feldnamen im JSON-Objekt und der definierte Spaltenname sollten übereinstimmen.
  • onRegisterApi - Die Möglichkeit, öffentliche Methodenereignisse im Raster zu registrieren. Hier haben wir gridApi.pagination.on.paginationChanged registriert , um UI-Grid anzuweisen , diese Funktion auszulösen, wenn die Seite geändert wurde.

Und um die Anfrage an die API zu senden:

app.service('StudentService',['$http', function ($http) { function getStudents(pageNumber,size) { pageNumber = pageNumber > 0?pageNumber - 1:0; return $http({ method: 'GET', url: 'student/get?page='+pageNumber+'&size='+size }); } return { getStudents: getStudents }; }]);

5. Das Backend und die API

5.1. Der ruhige Service

Hier ist die einfache RESTful-API-Implementierung mit Paginierungsunterstützung:

@RestController public class StudentDirectoryRestController { @Autowired private StudentService service; @RequestMapping( value = "/student/get", params = { "page", "size" }, method = RequestMethod.GET ) public Page findPaginated( @RequestParam("page") int page, @RequestParam("size") int size) { Page resultPage = service.findPaginated(page, size); if (page > resultPage.getTotalPages()) { throw new MyResourceNotFoundException(); } return resultPage; } }

Der @RestController wurde im Frühjahr 4.0 als praktische Annotation eingeführt, die implizit @Controller und @ResponseBody deklariert.

Für unsere API haben wir deklariert, dass zwei Parameter akzeptiert werden: Seite und Größe, die auch die Anzahl der Datensätze bestimmen, die an den Client zurückgegeben werden sollen.

Wir haben auch eine einfache Validierung hinzugefügt, die eine MyResourceNotFoundException auslöst, wenn die Seitenzahl höher als die Gesamtseiten ist.

Schließlich geben wir Page als Antwort zurück - dies ist eine sehr hilfreiche Komponente von S pring Data, die Paginierungsdaten enthält.

5.2. Die Service-Implementierung

Our service will simply return the records based on page and size provided by the controller:

@Service public class StudentServiceImpl implements StudentService { @Autowired private StudentRepository dao; @Override public Page findPaginated(int page, int size) { return dao.findAll(new PageRequest(page, size)); } } 

5.3. The Repository Implementation

For our persistence layer, we're using an embedded database and Spring Data JPA.

First, we need to setup our persistence config:

@EnableJpaRepositories("com.baeldung.web.dao") @ComponentScan(basePackages = { "com.baeldung.web" }) @EntityScan("com.baeldung.web.entity") @Configuration public class PersistenceConfig { @Bean public JdbcTemplate getJdbcTemplate() { return new JdbcTemplate(dataSource()); } @Bean public DataSource dataSource() { EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); EmbeddedDatabase db = builder .setType(EmbeddedDatabaseType.HSQL) .addScript("db/sql/data.sql") .build(); return db; } } 

The persistence config is simple – we have @EnableJpaRepositories to scan the specified package and find our Spring Data JPA repository interfaces.

We have the @ComponentScan here to automatically scan for all beans and we have @EntityScan (from Spring Boot) to scan for entity classes.

We also declared our simple datasource – using an embedded database that will run the SQL script provided on startup.

Now it's time we create our data repository:

public interface StudentRepository extends JpaRepository {} 

This is basically all that we need to do here; if you want to go deeper into how to set up and use the highly powerful Spring Data JPA, definitely read the guide to it here.

6. Pagination Request and Response

When calling the API – //localhost:8080/student/get?page=1&size=5, the JSON response will look something like this:

{ "content":[ {"studentId":"1","name":"Bryan","gender":"Male","age":20}, {"studentId":"2","name":"Ben","gender":"Male","age":22}, {"studentId":"3","name":"Lisa","gender":"Female","age":24}, {"studentId":"4","name":"Sarah","gender":"Female","age":26}, {"studentId":"5","name":"Jay","gender":"Male","age":20} ], "last":false, "totalElements":20, "totalPages":4, "size":5, "number":0, "sort":null, "first":true, "numberOfElements":5 } 

One thing to notice here is that server returns a org.springframework.data.domain.Page DTO, wrapping our Student Resources.

The Page object will have the following fields:

  • last – set to true if its the last page otherwise false
  • first – set to true if it's the first page otherwise false
  • totalElements – the total number of rows/records. In our example, we passed this to the ui-grid options $scope.gridOptions.totalItems to determine how many pages will be available
  • totalPages – the total number of pages which was derived from (totalElements / size)
  • size – the number of records per page, this was passed from the client via param size
  • number – the page number sent by the client, in our response the number is 0 because in our backend we are using an array of Students which is a zero-based index, so in our backend, we decrement the page number by 1
  • sort – the sorting parameter for the page
  • numberOfElements – the number of rows/records return for the page

7. Testing Pagination

Let's now set up a test for our pagination logic, using RestAssured; to learn more about RestAssured you can have a look at this tutorial.

7.1. Preparing the Test

For ease of development of our test class we will be adding the static imports:

io.restassured.RestAssured.* io.restassured.matcher.RestAssuredMatchers.* org.hamcrest.Matchers.*

Next, we'll set up the Spring enabled test:

@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration @IntegrationTest("server.port:8888") 

The @SpringApplicationConfiguration helps Spring know how to load the ApplicationContext, in this case, we used the Application.java to configure our ApplicationContext.

The @WebAppConfiguration was defined to tell Spring that the ApplicationContext to be loaded should be a WebApplicationContext.

And the @IntegrationTest was defined to trigger the application startup when running the test, this makes our REST services available for testing.

7.2. The Tests

Here is our first test case:

@Test public void givenRequestForStudents_whenPageIsOne_expectContainsNames() { given().params("page", "0", "size", "2").get(ENDPOINT) .then() .assertThat().body("content.name", hasItems("Bryan", "Ben")); } 

This test case above is to test that when page 1 and size 2 is passed to the REST service the JSON content returned from the server should have the names Bryan and Ben.

Let's dissect the test case:

  • given – the part of RestAssured and is used to start building the request, you can also use with()
  • get – the part of RestAssured and if used triggers a get request, use post() for post request
  • hasItems – the part of hamcrest that checks if the values have any match

We add a few more test cases:

@Test public void givenRequestForStudents_whenResourcesAreRetrievedPaged_thenExpect200() { given().params("page", "0", "size", "2").get(ENDPOINT) .then() .statusCode(200); }

This test asserts that when the point is actually called an OK response is received:

@Test public void givenRequestForStudents_whenSizeIsTwo_expectNumberOfElementsTwo() { given().params("page", "0", "size", "2").get(ENDPOINT) .then() .assertThat().body("numberOfElements", equalTo(2)); }

This test asserts that when page size of two is requested the pages size that is returned is actually two:

@Test public void givenResourcesExist_whenFirstPageIsRetrieved_thenPageContainsResources() { given().params("page", "0", "size", "2").get(ENDPOINT) .then() .assertThat().body("first", equalTo(true)); } 

This test asserts that when the resources are called the first time the first page name value is true.

There are many more tests in the repository, so definitely have a look at the GitHub project.

8. Conclusion

This article illustrated how to implement a data table grid using UI-Grid in AngularJS and how to implement the required server side pagination.

Die Implementierung dieser Beispiele und Tests finden Sie im GitHub-Projekt. Dies ist ein Maven-Projekt, daher sollte es einfach zu importieren und auszuführen sein.

Um das Spring-Boot-Projekt auszuführen, können Sie einfach mvn spring-boot: run ausführen und lokal auf // localhost: 8080 / zugreifen .

REST unten

Ich habe gerade den neuen Learn Spring- Kurs angekündigt , der sich auf die Grundlagen von Spring 5 und Spring Boot 2 konzentriert:

>> Überprüfen Sie den Kurs