知識庫 / Spring RSS 訂閱

Spring REST 與 AngularJS 表單分頁

REST,Spring
HongKong
6
03:56 AM · Dec 06 ,2025

1. 概述

本文主要介紹如何在 Spring REST API 和一個簡單的 AngularJS 前端中實現服務端分頁。

我們還將探索 Angular 中常用的表格網格 UI Grid (UI Grid)。

2. 依賴項

此處詳細列出了本文檔所需的各種依賴項。

2.1. JavaScript

為了使 Angular UI Grid 正常工作,我們需要在 HTML 中導入以下腳本。

2.2 Maven

為了我們的後端,我們將使用 Spring Boot,因此我們需要以下依賴項:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>

注意: 依賴項列表未在此處指定,完整列表請查閲 pom.xml 文件,位於 GitHub 項目中。

3. 關於應用程序

該應用程序是一個簡單的學生目錄應用程序,允許用户以分頁表格網格的形式查看學生詳情。

該應用程序使用 Spring Boot,並在嵌入式 Tomcat 服務器上運行,並使用嵌入式數據庫。

此外,在 API 層面,有幾種分頁方式可供選擇,具體描述請參考 REST Pagination in Spring 文章 – 這篇文章強烈建議與本文一起閲讀。

我們的解決方案非常簡單,即將分頁信息作為 URI 查詢參數,格式如下: /student/get?page=1&size=2

4. 客户端

首先,我們需要創建客户端邏輯。

4.1. UI-網格

我們的 index.html 將包含所需的導入以及一個簡單的表格網格實現:

<!DOCTYPE html>
<html lang="en" ng-app="app">
    <head>
        <link rel="stylesheet" href="https://cdn.rawgit.com/angular-ui/
          bower-ui-grid/master/ui-grid.min.css">
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/
          1.5.6/angular.min.js"></script>
        <script src="https://cdn.rawgit.com/angular-ui/bower-ui-grid/
          master/ui-grid.min.js"></script>
        <script src="view/app.js"></script>
    </head>
    <body>
        <div ng-controller="StudentCtrl as vm">
            <div ui-grid="gridOptions" class="grid" ui-grid-pagination>
            </div>
        </div>
    </body>
</html>

讓我們更詳細地看一下代碼:

  • ng-app – 是 Angular 指令,用於加載模塊 app。 這些元素都將屬於 app 模塊。
  • 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']);

我們聲明瞭 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<Student> findPaginated(
      @RequestParam("page") int page, @RequestParam("size") int size) {

        Page<Student> resultPage = service.findPaginated(page, size);
        if (page > resultPage.getTotalPages()) {
            throw new MyResourceNotFoundException();
        }

        return resultPage;
    }
}

@RestController 在 Spring 4.0 中作為一種便捷註解引入的,它隱式聲明瞭@Controller@ResponseBody

對於我們的 API,我們聲明它接受兩個參數,即page 和 size,這些參數也將決定返回給客户端的記錄數量。

我們還添加了一個簡單的驗證,如果頁碼大於總頁數,則會拋出MyResourceNotFoundException

最後,我們將返回Page 作為 Response – 這是一個 Spring Data 中非常有用的組件,它包含了分頁數據。

5.2. 服務實現

我們的服務將根據控制器提供的頁碼和大小,簡單地返回記錄:

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentRepository dao;

    @Override
    public Page<Student> 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 自動掃描所有 Bean,並且使用了 @EntityScan (來自 Spring Boot) 掃描實體類。

我們還聲明瞭我們的簡單數據源——使用嵌入式數據庫,該數據庫會在啓動時運行提供的 SQL 腳本。

現在是時候創建我們的數據存儲庫:

public interface StudentRepository extends JpaRepository<Student, Long> {}

這基本上就是我們需要做的全部內容;如果您想更深入地瞭解如何設置和使用功能強大的 Spring Data JPA,請務必閲讀該指南。

6. 分頁請求與響應

當調用 API – http://localhost:8080/student/get?page=1&amp;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
}

需要注意的是,服務器返回一個 org.springframework.data.domain.Page DTO,封裝了我們的 Student 資源。

Page 對象將具有以下字段:

  • last – 如果它是最後一頁則設置為 true,否則設置為 false
  • first – 如果它是第一頁則設置為 true,否則設置為 false
  • totalElements – 總行/記錄數。在我們的示例中,我們將其傳遞給 ui-grid 選項 $scope.gridOptions.totalItems 以確定將有多少頁可用
  • totalPages – 總頁數,該值是從 (totalElements / size) 計算得出的
  • size – 每頁記錄數,該值通過客户端參數 size 傳遞的
  • number – 客户端發送的頁碼,在我們的響應中,由於我們使用 Student 數組,該數組採用零索引,因此在後端,我們通過 1 減去頁碼
  • sort – 頁面的排序參數
  • numberOfElements – 頁面的行/記錄數

7. 測試分頁

現在,讓我們設置一個測試,用於驗證我們的分頁邏輯,使用 RestAssured。要了解更多關於 RestAssured 的信息,您可以查看這個教程。

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

上述測試用例旨在驗證當 page 1 和 size 2 傳遞給 REST 服務時,從服務器返回的 JSON 內容應包含名稱 BryanBen

讓我們分析一下測試用例:

  • givenRestAssured 的一部分,用於啓動請求,你也可以使用 with()
  • getRestAssured 的一部分,如果使用則觸發 GET 請求,使用 post() 用於 POST 請求
  • hasItemshamcrest 的一部分,用於檢查值是否存在匹配

我們添加了幾個額外的測試用例:

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

此測試斷言,當請求頁面大小為“2”時,返回的頁面大小確實為“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-GridAngularJS 中實現數據表格網格,以及如何實現所需的服務器端分頁。

要運行 Spring Boot 項目,只需執行 mvn spring-boot:run 命令,並在本地訪問 http://localhost:8080/

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.