Penomoran halaman dengan tabel Spring REST dan AngularJS

REST Top

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya

1. Ikhtisar

Pada artikel ini, kami terutama akan fokus pada penerapan pagination sisi server di Spring REST API dan front-end AngularJS sederhana.

Kami juga akan menjelajahi kisi tabel yang umum digunakan di Angular bernama UI Grid.

2. Dependensi

Di sini kami merinci berbagai dependensi yang diperlukan untuk artikel ini.

2.1. JavaScript

Agar Angular UI Grid berfungsi, kita memerlukan skrip di bawah ini yang diimpor dalam HTML kita.

  • JS Sudut (1.5.8)
  • Kisi UI Sudut

2.2. Maven

Untuk backend kami, kami akan menggunakan Spring Boot , jadi kami membutuhkan dependensi di bawah ini:

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

Catatan: Dependensi lain tidak ditentukan di sini, untuk daftar lengkapnya, periksa pom.xml lengkap di proyek GitHub.

3. Tentang Aplikasi

Aplikasi ini adalah aplikasi direktori siswa sederhana yang memungkinkan pengguna untuk melihat detail siswa dalam kisi tabel halaman.

Aplikasi ini menggunakan Spring Boot dan berjalan di server Tomcat tertanam dengan database tertanam.

Terakhir, di sisi API, ada beberapa cara untuk melakukan penomoran halaman, yang dijelaskan dalam artikel REST Pagination di Musim Semi di sini - yang sangat disarankan untuk dibaca sehubungan dengan artikel ini.

Solusi kami di sini sederhana - memiliki informasi paging dalam kueri URI sebagai berikut: / student / get? Page = 1 & size = 2 .

4. Sisi Klien

Pertama, kita perlu membuat logika sisi klien.

4.1. UI-Grid

Index.html kita akan memiliki impor yang kita butuhkan dan implementasi sederhana dari tabel grid:

Mari kita lihat lebih dekat kodenya:

  • ng-app - adalah direktif Angular yang memuat aplikasi modul . Semua elemen di bawah ini akan menjadi bagian dari modul aplikasi
  • ng-controller - adalah direktif Angular yang memuat controller StudentCtrl dengan alias vm. Semua elemen di bawah ini akan menjadi bagian dari pengontrol StudentCtrl
  • ui-grid - adalah direktif Angular milik Angular ui-grid dan menggunakan gridOptions sebagai pengaturan defaultnya, gridOptions dideklarasikan di bawah $ scope di app.js

4.2. Modul AngularJS

Pertama-tama mari kita definisikan modul di app.js :

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

Kami mendeklarasikan modul aplikasi dan kami memasukkan ui.grid untuk mengaktifkan fungsionalitas UI-Grid; kami juga memasukkan ui.grid.pagination untuk mengaktifkan dukungan pagination.

Selanjutnya, kami akan menentukan pengontrol:

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

Sekarang mari kita lihat pengaturan pagination kustom di $ scope.gridOptions :

  • paginationPageSizes - mendefinisikan opsi ukuran halaman yang tersedia
  • paginationPageSize - menentukan ukuran halaman default
  • enableColumnMenus - digunakan untuk mengaktifkan / menonaktifkan menu pada kolom
  • useExternalPagination - diperlukan jika Anda melakukan penomoran halaman di sisi server
  • columnDefs - nama kolom yang secara otomatis akan dipetakan ke objek JSON yang dikembalikan dari server. Nama kolom di Objek JSON yang dikembalikan dari server dan nama kolom yang ditentukan harus cocok.
  • onRegisterApi - kemampuan untuk mendaftarkan peristiwa metode publik di dalam grid. Di sini kami mendaftarkan gridApi.pagination.on.paginationChanged untuk memberi tahu UI-Grid untuk memicu fungsi ini setiap kali halaman diubah.

Dan untuk mengirim permintaan ke 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 dan API

5.1. Layanan RESTful

Berikut implementasi RESTful API sederhana dengan dukungan pagination:

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

The @RestController diperkenalkan di Spring 4.0 sebagai penjelasan kenyamanan yang secara implisit menyatakan @Controller dan @ResponseBody.

Untuk API kami, kami mendeklarasikannya untuk menerima dua parameter yaitu halaman dan ukuran yang juga akan menentukan jumlah record untuk dikembalikan ke klien.

Kami juga menambahkan validasi sederhana yang akan menampilkan MyResourceNotFoundException jika nomor halaman lebih tinggi dari total halaman.

Akhirnya, kita akan mengembalikan Halaman sebagai Respon - ini adalah komponen yang sangat berguna dari S pring Data yang telah menyimpan data pagination.

5.2. Pelaksanaan Layanan

Our service will simply return the records based on page and size provided by the controller:

@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. The Repository Implementation

For our persistence layer, we're using an embedded database and Spring Data JPA.

First, we need to setup our persistence config:

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

The persistence config is simple – we have @EnableJpaRepositories to scan the specified package and find our Spring Data JPA repository interfaces.

We have the @ComponentScan here to automatically scan for all beans and we have @EntityScan (from Spring Boot) to scan for entity classes.

We also declared our simple datasource – using an embedded database that will run the SQL script provided on startup.

Now it's time we create our data repository:

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.

Penerapan contoh dan pengujian ini dapat ditemukan di proyek GitHub. Ini adalah proyek Maven, jadi semestinya mudah untuk mengimpor dan menjalankannya apa adanya.

Untuk menjalankan proyek boot Spring, Anda cukup melakukan mvn spring-boot: run dan mengaksesnya secara lokal di // localhost: 8080 /.

REST bawah

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya