MongoDB là một trong những hệ thống database hỗ trợ cơ chế Reactive thông qua việc sử dụng MongoDB Reactive Streams Java Driver của nó. Cùng với Spring Data MongoDB, Spring cũng giới thiệu cho chúng ta module Spring Data MongoDB Reactive giúp chúng ta giảm thiểu những đoạn code lặp đi lặp lại khi sử dụng MongoDB Reactive Streams Java Driver. Trong bài viết này, thông qua một ví dụ xây dựng Reactive REST APIs, mình hướng dẫn các bạn cách sử dụng Spring Data MongoDB Reactive với Spring WebFlux các bạn nhé!
Đầu tiên, mình sẽ tạo một Spring Boot project với Reactive MongoDB và Reactive Web như sau:
để làm ví dụ.
Kết quả:
Project Lombok:
1 2 3 4 5 6 |
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.20</version> <scope>provided</scope> </dependency> |
Trong ví dụ này, mình sẽ tạo ra một ứng dụng cho phép người dùng có thể thêm mới thông tin sinh viên và lấy danh sách sinh viên với dữ liệu được lưu trữ trong MongDB database. Khi người dùng request tới ứng dụng của chúng ta thì mỗi khi một sinh viên mới được thêm vào, thông tin của sinh viên này sẽ được publish tới cho người dùng.
Trước khi đi vào nội dung chính của bài viết, có 2 thứ mình cần nói với các bạn trước.
Đầu tiên, mình sẽ nói với các bạn một số thông tin về Spring Data MongoDB Reactive.
Tương tự như Spring Data MongoDB, một interface tên là ReactiveMongoRepository đã được giới thiệu trong Spring Data MongoDB Reactive. Chúng ta có interface Repository là interface chính trong Spring Data project (xem thêm ở đây nhé các bạn) và với mô hình Reactive, chúng ta có các interface ReactiveCrudRepository, ReactiveSortingRepository extend từ interface Repository để phục vụ cho mô hình này. Và interface ReactiveMongoRepository của Spring Data MongoDB Reactive được extend từ interface ReactiveSortingRepository. Các bạn sẽ thấy rõ điều này qua hình ảnh sau:
và như các bạn thấy, implementation của interface ReactiveMongoRepository là class SimpleReactiveMongoRepository.
Cái thứ hai là để cho ứng dụng của chúng ta có thể đẩy thông tin sinh viên mỗi khi nó được thêm vào, chúng ta cần phải sử dụng MongoDB với capped collection.
Nguyên nhân là vì mặc định, với mỗi một câu query, khi người dùng đã nhận hết data cho câu query đó, MongoDB sẽ đóng cursor. Cursor trong MongoDB các bạn hiểu nôm na thì đó là một pointer trong 1 result set của một câu query. Việc close cursor của MongoDB làm cho việc tiếp tục nhận data từ câu query của chúng ta không thể xảy ra được nữa.
Để giải quyết vấn đề này, chúng ta sẽ sử dụng tailable cursor với capped collection của MongoDB. Capped collection là những collection có kích thước cố định, nghĩa là chúng ta chỉ có thể thêm một số lượng nhất định các document. Khi capped collection đã đầy thì những document mới sẽ override những document cũ nhất. Còn tailable cursor thì nếu các bạn đã sử dụng câu lệnh tail -f trong Linux thì có thể hình dung tailable cusor sẽ keep việc retrieve document sau khi người dùng đã lấy hết kết quả cho câu query đó.
Các bạn có thể tạo capped collection sử dụng câu lệnh sau trong MongoDB Shell:
1 |
db.createCollection( "<collection_name>", { capped: true, size: <collection_size> } ) |
Trong ví dụ của bài viết này, mình sẽ tạo một capped collection tên là student như sau:
1 |
db.createCollection( "student", { capped: true, size: 100000 } ) |
Kết quả:
OK, giờ chúng ta sẽ đi vào ví dụ chính của bài viết này các bạn nhé.
Đầu tiên, mình sẽ tạo mới một interface tên là StudentRepository extend từ Reactive MongoRepository với nội dung như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.huongdanjava.springwebfluxmongodb; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; import org.springframework.data.mongodb.repository.Tailable; import reactor.core.publisher.Flux; public interface StudentRepository extends ReactiveMongoRepository<Student, String> { @Tailable Flux<Student> findBy(); } |
với class Student có nội dung như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.huongdanjava.springwebfluxmongodb; import lombok.Getter; import lombok.Setter; @Setter @Getter public class Student { private String name; private String age; } |
Như các bạn thấy, mặc dù ReactiveMongoRepository đã hỗ trợ cho chúng ta phương thức findAll() để lấy tất cả danh sách sinh viên nhưng phương thức này không hỗ trợ tailable cursor nên ở đây mình phải định nghĩa thêm một phương thức findBy() có chức năng tương tự như phương thức findAll() cùng với annotation @Tailable. Phương thức findBy() này sẽ giúp chúng ta sử dụng chức năng tailable cursor để keep retrieve document sau khi người dùng đã lấy hết kết quả của câu query.
Tiếp theo, mình sẽ cấu hình phần kết nối tới MongoDB server.
Vì project ví dụ của chúng ta là một Spring Boot project nên việc cấu hình cho phần kết nối đến MongoDB server sẽ đơn giản hơn rất nhiều. Chúng ta sẽ không cần khởi tạo đối tượng MongoTemplate nữa. Việc cần làm là các bạn hãy mở tập tin application.properties và tuỳ theo việc các bạn đang sử dụng MongoDB server với authentication mode hay không mà cấu hình như bên dưới cho phù hợp nhé.
Đối với authenticate mode thì chúng ta cần khai báo username và password như sau:
1 2 3 4 |
spring.data.mongodb.uri=mongodb://<host>:<port>/<database_name> spring.data.mongodb.username=<username> spring.data.mongodb.password=<password> |
Còn không thì chỉ cần khai báo:
1 |
spring.data.mongodb.uri=mongodb://<host>:<port>/<database_name> |
là được.
Hiện tại, mình sử dụng MongoDB server mà không cần username, password nên mình chỉ cần khai báo như sau:
1 |
spring.data.mongodb.uri=mongodb://localhost:27017/mongodb_example |
Phần cuối cùng chúng ta cần làm là thêm Controller để expose 2 request: một request cho phép người dùng có thể lấy danh sách sinh viên và một request cho phép người dùng có thể thêm mới sinh viên.
Mình sẽ tạo StudentController có nội dung như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package com.huongdanjava.springwebfluxmongodb; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @RestController public class StudentController { @Autowired private StudentRepository studentRepository; @GetMapping(value = "/students", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<Student> findAllStudents() { return studentRepository.findBy(); } @PostMapping("/student") public Mono<Student> createStudent(@RequestBody Student student) { return studentRepository.save(student); } } |
Như các bạn thấy, mình đã build 2 request: “/students” với phương thức GET và produces là “text/event-stream” cho phép người dùng có thể lấy tất cả danh sách sinh viên và mỗi khi có sinh viên mới được thêm vào, thông tin của sinh viên này sẽ được đẩy xuống cho người dùng; “/student” với phương thức POST cho phép chúng ta có thể thêm mới sinh viên.
ReactiveMongoRepository đã cung cấp cho chúng ta các phương thức findAll() và save() nên chúng ta chỉ cần gọi để sử dụng mà thôi. Một điểm mà các bạn cần lưu ý là, thay vì return về một List<Student> như trong Spring Data MongoDB làm, ở đây Spring Data MongoDB Reactive sẽ return về một đối tượng Flux<Student> và tương tự phương thức save() cũng vậy, nó return về một đối tượng Mono<Student>.
OK, đến đây thì chúng ta đã hoàn thành các bước cần thiết để xây dựng ứng dụng ví dụ này. Giờ chạy thử nha các bạn.
Mình sẽ mở một cửa sổ trình duyệt để chạy request “/students” và sử dụng Postman để chạy request “/student” thêm mới sinh viên vào.
Mình sẽ sử dụng Postman để thêm mới sinh viên trước:
Lúc này, request “/students” sẽ có kết quả như sau:
Giờ, nếu các bạn thêm mới một sinh viên khác:
thì các bạn sẽ thấy, thông tin của sinh viên đó sẽ tự động được đẩy về cửa sổ trình duyệt với request “/students”.
Tạ Quang Hoàng
Anh cho em hỏi tí 😀
Tại sao khi addStudent trong MongoDB với capped=true thì được nhưng khi updateStudent hay deleteStudent thì MongoDB không cho?
Giải thích giúp em với và có cách nào giải quyết cho cái này không a.