Пагинация с Spring REST и AngularJS таблица

ПОЧИВКА Най-горе

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА

1. Общ преглед

В тази статия ще се съсредоточим главно върху внедряването на странична страница на сървъра в Spring REST API и прост преден край на AngularJS.

Също така ще изследваме често използвана таблична мрежа в Angular, наречена UI Grid.

2. Зависимости

Тук подробно описваме различни зависимости, които са необходими за тази статия.

2.1. JavaScript

За да работи Angular UI Grid, ще са ни необходими скриптове по-долу, импортирани в нашия HTML.

  • Ъглов JS (1.5.8)
  • Ъглова UI мрежа

2.2. Мейвън

За нашия бекенд ще използваме Spring Boot , така че ще ни трябват следните зависимости:

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

Забележка: Тук не са посочени други зависимости, за пълния списък проверете пълния pom.xml в проекта GitHub.

3. Относно приложението

Приложението е просто приложение за директория на студент, което позволява на потребителите да виждат подробностите за студента в мрежа с разбита таблица.

Приложението използва Spring Boot и работи във вграден сървър на Tomcat с вградена база данни.

И накрая, от страна на нещата с API, има няколко начина за извършване на пагинация, описани в статията REST Pagination in Spring тук - което е силно препоръчително да се чете заедно с тази статия.

Нашето решение тук е просто - разполагането с информация за пейджинг в URI заявка, както следва: / student / get? Page = 1 & size = 2 .

4. Клиентската страна

Първо, трябва да създадем логика от страна на клиента.

4.1. Потребителската мрежа

Нашият index.html ще има необходимия импорт и просто изпълнение на решетката на таблицата:

Нека разгледаме по-отблизо кода:

  • ng-app - е директивата Angular, която зарежда приложението на модула . Всички елементи под тях ще бъдат част от модула на приложението
  • ng-controller - е директивата Angular, която зарежда контролера StudentCtrl с псевдоним на vm. Всички елементи под тях ще бъдат част от контролера StudentCtrl
  • ui-grid - е Angular директивата, която принадлежи на Angular ui-grid и използва gridOptions като настройки по подразбиране, gridOptions се декларира под $ scope в app.js

4.2. Модулът AngularJS

Нека първо дефинираме модула в app.js :

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

Декларирахме модула на приложението и инжектирахме ui.grid, за да активираме функционалността на UI-Grid; ние също инжектирахме ui.grid.pagination, за да активираме поддръжката на пагинация.

След това ще дефинираме контролера:

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; }); }); } }; }]); 

Нека сега да разгледаме персонализираните настройки за страниране в $ scope.gridOptions :

  • paginationPageSizes - определя наличните опции за размер на страницата
  • paginationPageSize - определя размера на страницата по подразбиране
  • enableColumnMenus - използва се за активиране / деактивиране на менюто в колони
  • useExternalPagination - задължително е, ако правите странична страница от сървъра
  • columnDefs - имената на колоните, които автоматично ще бъдат съпоставени с обекта JSON, върнат от сървъра. Имената на полетата в JSON обекта, върнати от сървъра, и дефинираното име на колоната трябва да съвпадат.
  • onRegisterApi - възможността за регистриране на събития от публични методи в мрежата. Тук регистрирахме gridApi.pagination.on.paginationChanged, за да кажем на UI-Grid да задейства тази функция при всяка промяна на страницата.

И за да изпратите заявката до API:

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. Backend и API

5.1. Услугата RESTful

Ето простото изпълнение на RESTful API с поддръжка на пагинация:

@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; } }

В @RestController беше въведен през пролетта на 4.0 за удобство анотация, която безусловно декларира @Controller и @ResponseBody.

За нашия API декларирахме, че приема два параметъра, които са страница и размер, които също определят броя на записите, които трябва да се върнат на клиента.

Също така добавихме проста проверка, която ще хвърли MyResourceNotFoundException, ако номерът на страницата е по-голям от общия брой страници.

И накрая, ще върнем Page като отговор - това е супер полезен компонент на S pring Data, който съдържа данни за пагинация.

5.2. Изпълнението на услугата

Нашата услуга просто ще върне записите въз основа на страница и размер, предоставени от контролера:

@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. Реализацията на хранилището

За нашия слой на устойчивост използваме вградена база данни и Spring Data JPA.

Първо, трябва да настроим нашата конфигурация за постоянство:

@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; } } 

Конфигурацията за постоянство е проста - имаме @EnableJpaRepositories, за да сканираме посочения пакет и да намерим нашите интерфейси на хранилището Spring Data JPA.

Тук имаме @ComponentScan за автоматично сканиране за всички зърна и @EntityScan (от Spring Boot) за сканиране на класове обекти.

Също така декларирахме нашия прост източник на данни - използвайки вградена база данни, която ще изпълнява SQL скрипта, предоставен при стартиране.

Сега е време да създадем нашето хранилище на данни:

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.

Прилагането на тези примери и тестове може да бъде намерено в проекта GitHub. Това е проект на Maven, така че трябва да е лесно да се импортира и да се изпълнява както е.

За да стартирате проекта за зареждане на Spring, можете просто да направите mvn spring-boot: run и да го осъществите локално на // localhost: 8080 /.

ПОЧИВКА отдолу

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА