Paginering med Spring REST og AngularJS tabel

REST Top

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

1. Oversigt

I denne artikel vil vi hovedsageligt fokusere på implementering af serversiden pagination i en Foråret REST API og en simpel AngularJS front-end.

Vi undersøger også et almindeligt anvendt tabelgitter i Angular med navnet UI Grid.

2. Afhængigheder

Her beskriver vi forskellige afhængigheder, der kræves til denne artikel.

2.1. JavaScript

For at Angular UI Grid skal fungere, skal vi bruge nedenstående scripts importeret i vores HTML.

  • Vinkel JS (1.5.8)
  • Vinkel UI-gitter

2.2. Maven

Til vores backend bruger vi Spring Boot, så vi har brug for nedenstående afhængigheder:

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

Bemærk: Andre afhængigheder blev ikke specificeret her, for den fulde liste skal du kontrollere den komplette pom.xml i GitHub-projektet.

3. Om applikationen

Applikationen er en simpel studerendes biblioteksapp, der giver brugerne mulighed for at se studerendes detaljer i et pagineret tabelgitter.

Applikationen bruger Spring Boot og kører i en integreret Tomcat-server med en integreret database.

Endelig er der på API-siden af ​​tingene et par måder at gøre pagination på, beskrevet i REST Pagination in Spring-artiklen her - hvilket anbefales at læse i forbindelse med denne artikel.

Vores løsning her er enkel - at have personsøgningsoplysningerne i en URI-forespørgsel som følger: / student / get? page = 1 & size = 2.

4. Kundesiden

Først skal vi oprette logik på klientsiden.

4.1. UI-gitteret

Vores index.html vil have den import, vi har brug for, og en simpel implementering af bordgitteret:

Lad os se nærmere på koden:

  • ng-app - er vinkeldirektivet, der indlæser modulet app. Alle elementer under disse vil være en del af app modul
  • ng-controller - er vinkeldirektivet, der indlæser controlleren StudentCtrl med et alias for vm. Alle elementer under disse vil være en del af StudentCtrl controller
  • ui-gitter - er det vinkeldirektiv, der hører til vinklet ui-gitter og anvendelser gridOptions som standardindstillinger, gridOptions er erklæret under $ omfang i app.js

4.2. AngularJS-modulet

Lad os først definere modulet i app.js:

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

Vi erklærede app modul, og vi injicerede ui.net for at aktivere UI-Grid-funktionalitet vi injicerede også ui.grid.pagination for at aktivere paginering.

Dernæst definerer vi controlleren:

app.controller ('StudentCtrl', ['$ scope', 'StudentService', funktion ($ scope, StudentService) {var paginationOptions = {pageNumber: 1, pageSize: 5, sort: null}; StudentService.getStudents (paginationOptions.pageNumber , paginationOptions.pageSize) .success (funktion (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: funktion (gridApi) {$ scope.gridApi = gridApi; gridApi.pagination.on.paginationChanged ($ scope, function (newPage, pageSize) {paginationOptions.pageNumber = newPage; paginationOptions.pageSize = pageSize; StudentService. getStudents (newPage, pageSize) .success (funktion (data) {$ scope.gridOptions.data = data.indhold; $ scope.gridOptions.totalItems = data.totalElements;}); }); }}; }]); 

Lad os nu se på de tilpassede sideindstillinger i $ scope.gridOptions:

  • paginationPageSizes - definerer de tilgængelige sidestørrelsesindstillinger
  • paginationPageSize - definerer standard sidestørrelse
  • aktivereColumnMenus - bruges til at aktivere / deaktivere menuen i kolonner
  • useExternalPagination - kræves, hvis du sideskriver på serversiden
  • columnDefs - kolonnenavnene, der automatisk kortlægges til det JSON-objekt, der returneres fra serveren. Feltnavne i JSON-objektet, der returneres fra serveren, og det definerede kolonnenavn skal matche.
  • onRegisterApi - evnen til at registrere offentlige metodebegivenheder inden i nettet. Her registrerede vi gridApi.pagination.on.paginationChanged at bede UI-Grid om at udløse denne funktion, når siden blev ændret.

Og for at sende anmodningen til API:

app.service ('StudentService', ['$ http', funktion ($ http) {funktion getStudents (pageNumber, størrelse) {pageNumber = pageNumber> 0? pageNumber - 1: 0; returner $ http ({metode: 'GET' , url: 'student / get? page =' + pageNumber + '& size =' + size});} returner {getStudents: getStudents};}]);

5. Backend og API

5.1. Den RESTful Service

Her er den enkle RESTful API-implementering med pagineringsunderstøttelse:

@RestController offentlig klasse StudentDirectoryRestController {@Autowired privat StudentService-tjeneste; @RequestMapping (værdi = "/ student / get", params = {"side", "størrelse"}, metode = RequestMethod.GET) offentlig side findPaginated (@RequestParam ("side") int side, @RequestParam ("størrelse") ) int-størrelse) {Side resultPage = service.findPaginated (side, størrelse); hvis (side> resultPage.getTotalPages ()) {smid nyt MyResourceNotFoundException (); } return resultPage; }}

Det @RestController blev introduceret i foråret 4.0 som en bekvemmelighedsnotat, der implicit erklærer @Kontrol og @ResponseBody.

For vores API erklærede vi, at det accepterede to parametre, som er side og størrelse, der også vil bestemme antallet af poster, der skal returneres til klienten.

Vi tilføjede også en simpel validering, der vil kaste en MyResourceNotFoundException hvis sidetallet er højere end det samlede antal sider.

Endelig vender vi tilbage Side som svar - dette er en super hjælpsom komponent i Spring-data som har indeholdt pagineringsdata.

5.2. Tjenesteimplementeringen

Vores service returnerer simpelthen posterne baseret på side og størrelse leveret af controlleren:

@Service offentlig klasse StudentServiceImpl implementerer StudentService {@Autowired privat StudentRepository dao; @ Override offentlig side findPaginated (int-side, int-størrelse) {return dao.findAll (ny PageRequest (side, størrelse)); }} 

5.3. Repository Implementation

Til vores persistenslag bruger vi en indlejret database og Spring Data JPA.

Først skal vi konfigurere vores persistens-konfiguration:

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

Persistenskonfigurationen er enkel - det har vi @EnableJpaRepositories for at scanne den specificerede pakke og finde vores Spring Data JPA repository-grænseflader.

Vi har @ComponentScan her for automatisk at scanne efter alle bønner og vi har @EntityScan (fra Spring Boot) for at scanne efter enhedsklasser.

Vi erklærede også vores enkle datakilde - ved hjælp af en integreret database, der kører det SQL-script, der leveres ved opstart.

Nu er det tid, vi opretter vores datalager:

offentlig grænseflade StudentRepository udvider JpaRepository {} 

Dette er dybest set alt, hvad vi skal gøre her; Hvis du vil gå dybere ind i, hvordan du opsætter og bruger den meget kraftfulde Spring Data JPA, skal du helt sikkert læse vejledningen til den her.

6. Paginering Anmodning og svar

Når du ringer til API - // localhost: 8080 / student / get? Page = 1 & size = 5, vil JSON-svaret se sådan ud:

{"content": [{"studentId": "1", "name": "Bryan", "gender": "Male", "age": 20}, {"studentId": "2", "name" : "Ben", "køn": "Mand", "alder": 22}, {"studentId": "3", "navn": "Lisa", "køn": "Kvinde", "alder": 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} 

En ting at bemærke her er, at serveren returnerer en org.springframework.data.domæne.Side DTO, indpakning af vores Studerende Ressourcer.

Det Side objektet har følgende felter:

  • sidst - indstillet til rigtigt hvis det er den sidste side ellers falsk
  • først - indstillet til rigtigt hvis det er den første side ellers falsk
  • totalElements - det samlede antal rækker / poster. I vores eksempel overgav vi dette til ui-gitter muligheder $ scope.gridOptions.totalItems for at bestemme, hvor mange sider der vil være tilgængelige
  • totalSider - det samlede antal sider, der stammer fra (totalElements / størrelse)
  • størrelse - antallet af poster pr. Side, dette blev sendt fra klienten via param størrelse
  • nummer - det sidetal, der sendes af klienten, i vores svar er tallet 0, fordi vi i vores backend bruger en række Studerendes, som er et nulbaseret indeks, så i vores backend reduceres sidetallet med 1
  • sortere - sorteringsparameteren for siden
  • numberOfElements - antallet af rækker / poster, der returneres til siden

7. Test af pagination

Lad os nu oprette en test for vores pagineringslogik ved hjælp af RestAssured; for at lære mere om Stol trygt på du kan se på denne vejledning.

7.1. Forberedelse af testen

For at lette udviklingen af ​​vores testklasse vil vi tilføje den statiske import:

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

Dernæst opretter vi den fjederaktiverede test:

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

Det @SpringApplicationConfiguration hjælper foråret med at vide, hvordan man indlæser ApplicationContext, i dette tilfælde brugte vi Application.java for at konfigurere vores ApplicationContext.

Det @WebAppConfiguration blev defineret for at fortælle foråret, at ApplicationContext skal læsses skal være en WebApplicationContext.

Og @IntegrationTest blev defineret for at udløse start af applikationen, når testen køres, dette gør vores REST-tjenester tilgængelige til test.

7.2. Testene

Her er vores første testsag:

@ Test offentlig ugyldighed givenRequestForStudents_whenPageIsOne_expectContainsNames () {given (). Params ("page", "0", "size", "2"). Get (ENDPOINT) .then () .assertThat (). Body ("content.name ", hasItems (" Bryan "," Ben ")); } 

Denne testtilfælde ovenfor er for at teste, at når side 1 og størrelse 2 sendes til REST-tjenesten, skal JSON-indholdet, der returneres fra serveren, have navnene Bryan og Ben.

Lad os dissekere testsagen:

  • givet - den del af Stol trygt på og bruges til at begynde at opbygge anmodningen, kan du også bruge med()
  • - den del af Stol trygt på og hvis brugt udløser en get-anmodning, skal du bruge post () til postanmodning
  • hasItems - den del af hamcrest, der kontrollerer, om værdierne stemmer overens

Vi tilføjer et par flere testsager:

@Test offentligt ugyldigt givenRequestForStudents_whenResourcesAreRetrievedPaged_thenExpect200 () {given (). Params ("page", "0", "size", "2"). Get (ENDPOINT) .then () .statusCode (200); }

Denne test hævder, at når punktet faktisk kaldes, modtages et OK-svar:

@ Test offentlig ugyldighed givenRequestForStudents_whenSizeIsTwo_expectNumberOfElementsTwo () {given (). Params ("page", "0", "size", "2"). Get (ENDPOINT) .then () .assertThat (). Body ("numberOfElements", lige til (2)); }

Denne test hævder, at når der anmodes om sidestørrelse på to, er sidestørrelsen, der returneres, faktisk to:

@Test offentligt ugyldigt givenResourcesExist_whenFirstPageIsRetrieved_thenPageContainsResources () {given (). Params ("page", "0", "size", "2"). Get (ENDPOINT) .then () .assertThat (). Body ("first", equalTo (sand)); } 

Denne test hævder, at når ressourcerne kaldes første gang, er værdien for første side navn sand.

Der er mange flere tests i lageret, så se bestemt på GitHub-projektet.

8. Konklusion

Denne artikel illustrerede, hvordan man implementerer et datatabelgitter ved hjælp af UI-gitter i AngularJS og hvordan man implementerer den krævede paginering på serversiden.

Implementeringen af ​​disse eksempler og tests findes i GitHub-projektet. Dette er et Maven-projekt, så det skal være let at importere og køre som det er.

For at køre Spring boot-projektet kan du bare gøre det mvn spring-boot: løb og få adgang til det lokalt // localhost: 8080 /.

REST bunden

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

$config[zx-auto] not found$config[zx-overlay] not found