Paginering met Spring REST en AngularJS-tafel

REST Top

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS

1. Overzicht

In dit artikel zullen we ons voornamelijk concentreren op het implementeren van paginering aan de serverzijde in een Spring REST API en een simpele AngularJS front-end.

We zullen ook een veelgebruikt tabelraster in Angular verkennen met de naam UI Grid.

2. Afhankelijkheden

Hier beschrijven we verschillende afhankelijkheden die vereist zijn voor dit artikel.

2.1. JavaScript

Om Angular UI Grid te laten werken, hebben we de onderstaande scripts nodig die in onze HTML zijn geïmporteerd.

  • Hoekige JS (1.5.8)
  • Hoekig UI-raster

2.2. Maven

Voor onze backend zullen we gebruiken Spring Boot, dus we hebben de onderstaande afhankelijkheden nodig:

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

Opmerking: Andere afhankelijkheden werden hier niet gespecificeerd, voor de volledige lijst, vink het volledige pom.xml in het GitHub-project.

3. Over de toepassing

De applicatie is een eenvoudige studentenlijst-app waarmee gebruikers de studentgegevens in een gepagineerd tabelraster kunnen zien.

De applicatie gebruikt Spring Boot en draait op een ingesloten Tomcat-server met een ingesloten database.

Tot slot, aan de API-kant van de dingen, zijn er een paar manieren om paginering uit te voeren, beschreven in het artikel REST Pagination in Spring hier - wat ten zeerste wordt aanbevolen om samen met dit artikel te lezen.

Onze oplossing is hier eenvoudig: de paging-informatie in een URI-query als volgt hebben: / student / get? page = 1 & size = 2.

4. De klantzijde

Eerst moeten we de logica aan de clientzijde creëren.

4.1. Het UI-Grid

Onze index.html heeft de invoer die we nodig hebben en een eenvoudige implementatie van het tabelraster:

Laten we de code eens nader bekijken:

  • ng-app - is de Angular-richtlijn die de module laadt app. Alle elementen onder deze zullen deel uitmaken van het app module
  • ng-controller - is de Angular-richtlijn die de controller laadt StudentCtrl met een alias van vm. Alle elementen onder deze zullen deel uitmaken van het StudentCtrl controller
  • ui-grid - is de Angular-richtlijn die bij Angular hoort ui-grid en toepassingen gridOptions als standaardinstellingen, gridOptions wordt verklaard onder $ scope in app.js

4.2. De AngularJS-module

Laten we eerst de module definiëren in app.js:

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

We hebben het app module en we hebben geïnjecteerd ui.grid om UI-Grid-functionaliteit in te schakelen; we hebben ook geïnjecteerd ui.grid.pagination om ondersteuning voor paginering in te schakelen.

Vervolgens definiëren we de controller:

app.controller ('StudentCtrl', ['$ scope', 'StudentService', functie ($ 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: [{naam: 'id'}, {naam: 'naam'}, {naam: 'geslacht'}, {naam: 'leeftijd '}], 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 (functie (data) {$ scope.gridOptions.data = data.content; $ scope.gridOptions.totalItems = data.totalElements;}); }); }}; }]); 

Laten we nu eens kijken naar de aangepaste pagineringsinstellingen in $ scope.gridOptions:

  • paginationPageSizes - definieert de beschikbare opties voor paginaformaat
  • paginationPageSize - definieert het standaard paginaformaat
  • enableColumnMenus - wordt gebruikt om het menu op kolommen in / uit te schakelen
  • useExternalPagination - is vereist als u aan de serverzijde pagineert
  • columnDefs - de kolomnamen die automatisch worden toegewezen aan het JSON-object dat wordt geretourneerd door de server. De veldnamen in het JSON-object die door de server worden geretourneerd en de gedefinieerde kolomnaam moeten overeenkomen.
  • onRegisterApi - de mogelijkheid om openbare methodegebeurtenissen binnen het raster te registreren. Hier hebben we het gridApi.pagination.on.paginationChanged om UI-Grid te vertellen om deze functie te activeren wanneer de pagina werd gewijzigd.

En om het verzoek naar de API te sturen:

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. De backend en de API

5.1. De rustgevende service

Hier is de eenvoudige RESTful API-implementatie met ondersteuning voor paginering:

@RestController openbare klas StudentDirectoryRestController {@Autowired privé StudentService-service; @RequestMapping (value = "/ student / get", params = {"page", "size"}, method = RequestMethod.GET) openbare pagina findPaginated (@RequestParam ("page") int pagina, @RequestParam ("size" ) int size) {Page resultPage = service.findPaginated (pagina, grootte); if (pagina> resultPage.getTotalPages ()) {gooi nieuwe MyResourceNotFoundException (); } return resultPage; }}

De @RestController werd geïntroduceerd in Spring 4.0 als een gemakkelijke annotatie die impliciet verklaart @Controller en @ResponseBody.

Voor onze API hebben we verklaard dat het twee parameters accepteert, namelijk bladzijde en de grootte die ook het aantal records zou bepalen dat naar de klant moet worden teruggestuurd.

We hebben ook een eenvoudige validatie toegevoegd die een MyResourceNotFoundException als het paginanummer hoger is dan het totale aantal pagina's.

Eindelijk keren we terug Bladzijde als antwoord - dit is een super handig onderdeel van Spring-gegevens die pagineringsgegevens heeft bewaard.

5.2. De service-implementatie

Onze service retourneert eenvoudig de records op basis van pagina en grootte die door de controller zijn verstrekt:

@Service openbare klas StudentServiceImpl implementeert StudentService {@Autowired privé StudentRepository dao; @Override openbare pagina findPaginated (int pagina, int grootte) {return dao.findAll (nieuwe PageRequest (pagina, grootte)); }} 

5.3. De Repository-implementatie

Voor onze persistentielaag gebruiken we een ingesloten database en Spring Data JPA.

Eerst moeten we onze persistentie-configuratie instellen:

@EnableJpaRepositories ("com.baeldung.web.dao") @ComponentScan (basePackages = {"com.baeldung.web"}) @EntityScan ("com.baeldung.web.entity") @Configuratie openbare klasse PersistenceConfig {@Bean openbaar JdbcTemplate getJdbcTemplate () {retourneer nieuwe JdbcTemplate (dataSource ()); } @Bean openbare DataSource dataSource () {EmbeddedDatabaseBuilder builder = nieuwe EmbeddedDatabaseBuilder (); EmbeddedDatabase db = builder .setType (EmbeddedDatabaseType.HSQL) .addScript ("db / sql / data.sql") .build (); terugkeer db; }} 

De persistentieconfiguratie is eenvoudig - we hebben @EnableJpaRepositories om het opgegeven pakket te scannen en onze Spring Data JPA-repository-interfaces te vinden.

We hebben de @BuienRadarNL hier om automatisch te scannen op alle bonen en wij hebben @EntityScan (van Spring Boot) om te scannen op entiteitsklassen.

We hebben ook onze eenvoudige gegevensbron gedeclareerd - met behulp van een ingesloten database die het SQL-script zal uitvoeren dat bij het opstarten wordt verstrekt.

Nu is het tijd dat we onze gegevensopslagplaats maken:

openbare interface StudentRepository breidt JpaRepository {} uit 

Dit is eigenlijk alles wat we hier hoeven te doen; als je dieper wilt ingaan op het instellen en gebruiken van de zeer krachtige Spring Data JPA, lees dan zeker de handleiding hier.

6. Paginatieverzoek en reactie

Bij het aanroepen van de API - // localhost: 8080 / student / get? Page = 1 & size = 5, zal het JSON-antwoord er ongeveer zo uitzien:

{"content": [{"studentId": "1", "name": "Bryan", "geslacht": "Man", "leeftijd": 20}, {"studentId": "2", "naam" : "Ben", "geslacht": "Mannelijk", "leeftijd": 22}, {"studentId": "3", "naam": "Lisa", "geslacht": "Vrouwelijk", "leeftijd": 24 }, {"studentId": "4", "name": "Sarah", "geslacht": "Vrouwelijk", "leeftijd": 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} 

Een ding dat hier moet worden opgemerkt, is dat de server een org.springframework.data.domain.Page DTO, ons inpakken Leerling Middelen.

De Bladzijde object zal de volgende velden hebben:

  • laatste - ingesteld op waar als het de laatste pagina is, anders false
  • eerste - ingesteld op waar als het de eerste pagina is, anders false
  • totalElements - het totale aantal rijen / records. In ons voorbeeld hebben we dit doorgegeven aan de ui-grid opties $ scope.gridOptions.totalItems om te bepalen hoeveel pagina's beschikbaar zullen zijn
  • totalPages - het totale aantal pagina's dat is afgeleid van (totaal Elementen / grootte)
  • grootte - het aantal records per pagina, dit werd via param grootte
  • aantal - het paginanummer dat door de klant is verzonden, in ons antwoord is het nummer 0 omdat we in onze backend een array van Leerlings wat een op nul gebaseerde index is, dus in onze backend verlagen we het paginanummer met 1
  • soort - de sorteerparameter voor de pagina
  • numberOfElements - het aantal rijen / records dat voor de pagina wordt geretourneerd

7. Paginering testen

Laten we nu een test opzetten voor onze paginatielogica, met behulp van RestAssured; om meer te weten te komen over Wees gerustgesteld je kunt deze tutorial bekijken.

7.1. Voorbereiding van de test

Om de ontwikkeling van onze testklasse te vergemakkelijken, zullen we de statische invoer toevoegen:

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

Vervolgens gaan we de Spring-enabled test opzetten:

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

De @SpringApplicationConfiguration helpt Spring te weten hoe de Toepassingscontext, in dit geval hebben we de Application.java om onze ApplicationContext.

De @WebAppConfiguration werd gedefinieerd om Spring te vertellen dat de ApplicationContext te laden moet een WebApplicationContext.

En de @Integratietest werd gedefinieerd om het opstarten van de applicatie te activeren tijdens het uitvoeren van de test, dit maakt onze REST-services beschikbaar voor testen.

7.2. De testen

Hier is onze eerste testcase:

@Test openbare leegte gegevenRequestForStudents_whenPageIsOne_expectContainsNames () {gegeven (). Params ("pagina", "0", "grootte", "2"). Get (ENDPOINT) .then () .assertThat (). Body ("content.name ", hasItems (" Bryan "," Ben ")); } 

Deze testcase hierboven is om te testen dat wanneer pagina 1 en grootte 2 worden doorgegeven aan de REST-service, de JSON-inhoud die wordt geretourneerd door de server de namen moet hebben Bryan en Ben.

Laten we de testcase ontleden:

  • gegeven - het deel van Wees gerustgesteld en wordt gebruikt om te beginnen met het bouwen van het verzoek, u kunt ook gebruiken met()
  • krijgen - het deel van Wees gerustgesteld en indien gebruikt een get-verzoek triggert, gebruik dan post () voor post-verzoek
  • hasItems - het deel van hamcrest dat controleert of de waarden overeenkomen

We voegen nog een paar testcases toe:

@Test openbare ongeldig gegevenRequestForStudents_whenResourcesAreRetrievedPaged_thenExpect200 () {gegeven (). Params ("pagina", "0", "grootte", "2"). Get (ENDPOINT) .then () .statusCode (200); }

Deze test stelt dat wanneer het punt daadwerkelijk wordt aangeroepen, een OK-antwoord wordt ontvangen:

@Test openbare leegte gegevenRequestForStudents_whenSizeIsTwo_expectNumberOfElementsTwo () {gegeven (). Params ("pagina", "0", "grootte", "2"). Get (ENDPOINT) .then () .assertThat (). Body ("numberOfElements", gelijk aan (2)); }

Deze test stelt dat wanneer een paginagrootte van twee wordt aangevraagd, het geretourneerde paginagrootte in feite twee is:

@Test openbare leegte gegevenResourcesExist_whenFirstPageIsRetrieved_thenPageContainsResources () {gegeven (). Params ("pagina", "0", "grootte", "2"). Get (ENDPOINT) .then () .assertThat (). Body ("eerste", gelijk aan (waar)); } 

Deze test stelt dat wanneer de bronnen de eerste keer worden aangeroepen, de waarde van de eerste paginanaam waar is.

Er zijn veel meer tests in de repository, dus kijk zeker eens naar het GitHub-project.

8. Conclusie

In dit artikel wordt geïllustreerd hoe u een gegevenstabelraster implementeert met UI-Grid in AngularJS en hoe de vereiste paginering aan de serverzijde moet worden geïmplementeerd.

De implementatie van deze voorbeelden en tests is te vinden in het GitHub-project. Dit is een Maven-project, dus het moet gemakkelijk te importeren en uit te voeren zijn zoals het is.

Om het Spring-opstartproject uit te voeren, kunt u het gewoon doen mvn spring-boot: run en open het lokaal op // localhost: 8080 /.

REST onder

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS