Нумерация страниц с помощью Spring REST и таблицы AngularJS

Нумерация страниц с помощью Spring REST и AngularJS таблицы

1. обзор

В этой статье мы в основном сосредоточимся на реализации разбивки на страницы на стороне сервера вSpring REST API и простом интерфейсе AngularJS.

Мы также рассмотрим часто используемую сетку таблиц в Angular с именемUI Grid.

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

Здесь мы подробно описываем различные зависимости, необходимые для этой статьи.

2.1. JavaScript

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

2.2. специалист

Для нашего бэкэнда мы будем использоватьSpring Boot, поэтому нам понадобятся следующие зависимости:


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


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

Note: Другие зависимости здесь не указаны, для получения полного списка проверьте полныйpom.xml в проектеGitHub.

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

Приложение представляет собой простую программу-справочник учащегося, которая позволяет пользователям просматривать сведения об учащихся в виде таблицы с разбивкой на страницы.

Приложение используетSpring Boot и работает на встроенном сервере Tomcat со встроенной базой данных.

Наконец, что касается API, есть несколько способов разбивки на страницы, описанные вREST Pagination in Spring article here, которые настоятельно рекомендуется прочитать вместе с этой статьей.

Наше решение здесь простое - информация о подкачке в запросе URI выглядит следующим образом:/student/get?page=1&size=2.

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

Во-первых, нам нужно создать логику на стороне клиента.

4.1. UI-сетка

Нашindex.html будет иметь необходимый импорт и простую реализацию сетки таблицы:



    
        
        
        
        
    
    
        

Давайте подробнее рассмотрим код:

  • ng-app - это директива Angular, которая загружает модульapp. Все элементы под ними будут частью модуляapp

  • ng-controller - это директива Angular, которая загружает контроллерStudentCtrl с псевдонимомvm.. Все элементы под ними будут частью контроллераStudentCtrl

  • ui-grid - это директива Angular, которая принадлежит Angularui-grid и используетgridOptions в качестве настроек по умолчанию,gridOptions объявлен в$scope вapp.js

4.2. Модуль AngularJS

Сначала определим модуль вapp.js:

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

Мы объявили модульapp и внедрили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. Бэкэнд и 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 был введен в Spring 4.0 как удобная аннотация, которая неявно объявляет@Controller и@ResponseBody.

Для нашего API мы объявили, что он принимает два параметра:page и размер, которые также будут определять количество записей, возвращаемых клиенту.

Мы также добавили простую проверку, которая выдастMyResourceNotFoundException, если номер страницы больше, чем общее количество страниц.

Наконец, мы вернемPage в качестве ответа - это очень полезный компонент Spring 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("org.example.web.dao")
@ComponentScan(basePackages = { "org.example.web" })
@EntityScan("org.example.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 для автоматического сканирования всех beans и мыhave @EntityScan (из Spring Boot) для сканирования классов сущностей.

Мы также объявили наш простой источник данных - использование встроенной базы данных, которая будет запускать сценарий SQL, предоставляемый при запуске.

Пришло время создать репозиторий данных:

public interface StudentRepository extends JpaRepository {}

Это в основном все, что нам здесь нужно сделать; если вы хотите глубже понять, как настроить и использовать очень мощный Spring Data JPA, определенноread the guide to it here.

6. Запрос и ответ пагинации

При вызове APIhttp://localhost:8080/student/get?page=1&size=5 ответ JSON будет выглядеть примерно так:

{
    "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
}

Здесь следует отметить, что сервер возвращает DTOorg.springframework.data.domain.Page, обертывая наши ресурсыStudent.

ОбъектPage будет иметь следующие поля:

  • last - установить наtrue, если это последняя страница, иначе false

  • first - установитеtrue, если это первая страница, иначе false

  • totalElements - общее количество строк / записей. В нашем примере мы передали это параметруui-grid$scope.gridOptions.totalItems, чтобы определить, сколько страниц будет доступно

  • totalPages - общее количество страниц, полученных из (totalElements / size)

  • size - количество записей на странице, это было передано от клиента через параметрsize

  • number - номер страницы, отправленный клиентом, в нашем ответе номер 0, потому что в нашем бэкэнде мы используем массивStudents, который является индексом, отсчитываемым от нуля, поэтому в нашем бэкэнде мы уменьшить номер страницы на 1

  • sort - параметр сортировки страницы

  • numberOfElements - количество строк / записей, возвращаемых для страницы

7. Тестирование разбивки на страницы

Теперь давайте настроим тест для нашей логики разбивки на страницы, используяRestAssured; чтобы узнать больше оRestAssured, вы можете взглянуть на этотtutorial.

7.1. Подготовка к тесту

Для простоты разработки нашего тестового класса мы добавим статический импорт:

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

Затем мы настроим тест с включенной Spring:

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

@SpringApplicationConfiguration помогает Spring узнать, как загрузитьApplicationContext, в этом случае, мы использовалиApplication.java для настройки нашегоApplicationContext.

@WebAppConfiguration был определен, чтобы сообщить Spring, что загружаемыйApplicationContext должен бытьWebApplicationContext.

И@IntegrationTest был определен для запуска запуска приложения при запуске теста, это делает наши службы REST доступными для тестирования.

7.2. Тесты

Вот наш первый тестовый пример:

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

Этот тестовый пример выше предназначен для проверки того, что когда страница 1 и размер 2 передаются в службу REST, содержимое JSON, возвращаемое с сервера, должно иметь именаBryan иBen..

Давайте разберем тестовый пример:

  • given - частьRestAssured и используется для начала построения запроса, вы также можете использоватьwith()

  • get - частьRestAssured, и если используется, запускает запрос на получение, используйте post () для запроса на публикацию

  • hasItems - часть hamcrest, которая проверяет, совпадают ли значения

Мы добавим еще несколько тестов:

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

Этот тест утверждает, что когда точка фактически вызвана, получен ответ OK:

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

Этот тест утверждает, что когда запрашивается размер страницы два, размер возвращаемых страниц фактически равен двум:

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

В этом тесте утверждается, что при первом вызове ресурсов значение имени первой страницы равно true.

В репозитории гораздо больше тестов, так что обязательно взгляните на проектGitHub.

8. Заключение

В этой статье показано, как реализовать сетку таблицы данных с использованиемUI-Grid вAngularJS и как реализовать необходимую разбивку на страницы на стороне сервера.

Реализацию этих примеров и тестов можно найти вGitHub project. Это проект Maven, поэтому его легко импортировать и запускать как есть.

Чтобы запустить проект загрузки Spring, вы можете просто выполнитьmvn spring-boot:run и получить к нему доступ локально наhttp://localhost:8080/.