Spring RESTとAngularJSテーブルによるページ付け

Spring RESTおよびAngularJSテーブルのページネーション

1. 概要

この記事では、主にサーバー側のページ付けをSpring REST APIと単純なAngularJSフロントエンドに実装することに焦点を当てます。

また、UI Gridという名前のAngularで一般的に使用されるテーブルグリッドについても説明します。

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:他の依存関係はここでは指定されていません。完全なリストについては、GitHubプロジェクトの完全なpom.xmlを確認してください。

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 –モジュールappをロードするAngularディレクティブです。 これらの下にあるすべての要素は、appモジュールの一部になります

  • ng-controller –コントローラーStudentCtrlにエイリアスvm.をロードするAngularディレクティブです。これらの下にあるすべての要素は、StudentCtrlコントローラーの一部になります。

  • ui-grid – Angularui-gridに属し、デフォルト設定としてgridOptionsを使用するAngularディレクティブです。gridOptionsapp.js$scopeで宣言されます

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サービス

ページネーションをサポートするシンプルなRESTfulAPIの実装は次のとおりです。

@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は、@Controller@ResponseBody.を暗黙的に宣言する便利なアノテーションとしてSpring4.0で導入されました。

APIの場合、pageとサイズの2つのパラメーターを受け入れるように宣言しました。これらのパラメーターは、クライアントに返すレコードの数も決定します。

また、ページ数が合計ページ数よりも多い場合に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 DataJPAを使用しています。

まず、永続化設定をセットアップする必要があります。

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

永続性の構成は単純です。指定されたパッケージをスキャンしてSpringData JPAリポジトリー・インターフェースを見つけるための@EnableJpaRepositoriesがあります。

ここには、すべてのBeanを自動的にスキャンするための@ComponentScanがあり、エンティティクラスをスキャンするために(Spring Bootから)have @EntityScanがあります。

起動時に提供されるSQLスクリプトを実行する組み込みデータベースを使用して、単純なデータソースも宣言しました。

次に、データリポジトリを作成します。

public interface StudentRepository extends JpaRepository {}

基本的に、ここで行う必要があるのはこれだけです。非常に強力なSpringData 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
}

ここで注意すべきことの1つは、サーバーがorg.springframework.data.domain.Page DTOを返し、Studentリソースをラップしていることです。

Pageオブジェクトには、次のフィールドがあります。

  • last –最後のページがfalseの場合はtrueに設定

  • first –最初のページの場合はtrueに設定し、そうでない場合はfalseに設定します

  • totalElements –行/レコードの総数。 この例では、これをui-gridオプション$scope.gridOptions.totalItemsに渡して、使用可能なページ数を決定します。

  • totalPages –(totalElements / size)から派生したページの総数

  • size –ページあたりのレコード数。これはクライアントからパラメーターsizeを介して渡されました。

  • number –クライアントから送信されたページ番号。バックエンドではゼロベースのインデックスであるStudentsの配列を使用しているため、応答では番号は0です。したがって、バックエンドでは、ページ番号を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は、ロードされるApplicationContextWebApplicationContext.であることをSpringに通知するために定義されました。

また、@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コンテンツの名前がBryanBen.であることをテストするためのものです。

テストケースを分析してみましょう。

  • givenRestAssuredの一部であり、リクエストの作成を開始するために使用されます。with()を使用することもできます。

  • getRestAssuredの一部であり、使用するとgetリクエストがトリガーされる場合は、postリクエストに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));
}

このテストは、ページサイズ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. 結論

この記事では、AngularJSUI-Gridを使用してデータテーブルグリッドを実装する方法と、必要なサーバー側のページ付けを実装する方法について説明しました。

これらの例とテストの実装は、GitHub projectにあります。 これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。

Springブートプロジェクトを実行するには、mvn spring-boot:runを実行し、http://localhost:8080/.でローカルにアクセスします。