Hướng dẫn aggregation spring data mongodb - tổng hợp dữ liệu mùa xuân mongodb
Lời mở đầuNhư chúng ta đã biết, việc sử dụng Aggregation trong mongoDb để thực hiện xử lý dữ liệu trong MongoDb là rất cần thiết. Đây là một framework mạnh mẽ linh hoạt trong quá trình truy vấn và xử lý dữ liệu với các toán tử mạnh mẽ cũng như hỗ trợ xử lý dữ liệu với số lượng lớn. Loạt bài trước, tác giả đã miêu tả tổng quan về Aggregation framework trong mongoDb. Các bạn có thể tìm hiểu tại đây : https://viblo.asia/p/tim-hieu-ve-aggregation-framework-trong-mongodb-Az45brRV5xY . Bài viết này sẽ đi sâu vào tìm hiểu cách tích hợp Aggregation vào Spring để có thể linh hoạt hơn trong việc xử lý dữ liệu Show Các lưu ý cần thiếtCác công cơ bản.
Lưu ý
Các thuật ngữ thường dùng trong bài viếtKhác với hệ cơ sở dữ liệu RDBMS, Spring data MongoDb dùng thuật ngữ khác để thực hiện ánh xạ, xử lý và thao tác trên MongoDb, Dưới đây là bảng so sánh giữa 2 hệ cở sở dữ liệu và sự khác nhau của nó
Bài viết sẽ thực hiện dùng Aggregation thao tác với Collection DB tên là Employees gồm các thông tin như phía dưới :
Thực hiệnTrước tiên cần tiến hành tạo một project bằng spring boot bằng cách truy cập vào trang start.spring.io với các thông tin cấu hình như phía dưới : Về thông tin cấu hình : Group và Artifact có thể tùy chọn đặt tên . Tuy nhiên nên đặt theo quy tắc : com.tên company hoặc cá nhân. tên project muốn đặt. Ở mục Dependencies chọn 2 dependency là Spring Web và Spring Data Mongo Db. Dependency Lombok có thể tùy chọn có hoặc không. Tiếp đến để tiến hành kết nối với cơ sở dữ liệu database, chúng ta cần định cấu hình kết nối với MongoDb trong file applications.properties hoặc application.yml như phía dưới : Applications.properties :
Cấu hình trên chỉ ra server đang chạy ở port 8989, spring.data.mongodb.uri định cấu hình connect với database với các thông số như username là root password là root , port 27017(mặc định của mongoDb) và database là BaoTrung. Các thông số tiếp theo chỉ định việc show các lệnh khi thao tác với mongoDb cũng như các level log khi thực hiện thao tác. Trong loạt bài viết đang sử dụng ở mức debug.port 8989, spring.data.mongodb.uri định cấu hình connect với database với các thông số như username là root password là root , port 27017(mặc định của mongoDb) và database là BaoTrung. Có một lưu ý ở đây : Trong bài viết đang sử dụng Mongo 3.0 Java driver ,cấu hình sẽ thông qua uri. Việc cấu hình như phía dưới sẽ không thành công đối với phiên bản Mongo 3.0 Java driver trở lên (chỉ áp dụng với Mongo 3.0 Java driver trở xuống) . Lý do là spring.data.mongodb.host và spring.data.mongodb.port đã bị loại bỏ trong Mongo 3.0 Java driverMongo 3.0 Java driver ,cấu hình sẽ thông qua uri. Việc cấu hình như phía dưới sẽ không thành công đối với phiên bản Mongo 3.0 Java driver trở lên (chỉ áp dụng với Mongo 3.0 Java driver trở xuống) . Lý do là spring.data.mongodb.host và spring.data.mongodb.port đã bị loại bỏ trong Mongo 3.0 Java driver //Không áp dụng với Mongo 3.0 Java Driver trở lên
Model Thực hiện tạo một class có tên là Employee như phía dưới :Employee như phía dưới : Employee.class
Lớp trên chỉ đơn giản là dùng @Document xác định nó là một thực thể để có thể thao tác với MongoDb với các trường như FirstName, LastName, Salary ,Age, Location . Có một lưu ý ở đây là do ở MongoDb đang sử dụng document tên là Employees nên nếu muốn ánh xạ đúng tên document tên là Employess thì chúng ta sẽ sử dụng 1@Document xác định nó là một thực thể để có thể thao tác với MongoDb với các trường như FirstName, LastName, Salary ,Age, Location . Có một lưu ý ở đây là do ở MongoDb đang sử
dụng document tên là Employees nên nếu muốn ánh xạ đúng tên document tên là Employess thì chúng ta sẽ sử dụng 1Tạo một lớp tên là EmployeeController và thực hiện xác định các api cần thiết cho việc xử lý dữ liệu. Ở đây chúng ta sẽ xác định các api như findByLocation , calculateTotalUser và calculateSalaryEmployeeController và thực hiện xác định các api cần thiết cho việc xử lý dữ liệu. Ở đây chúng ta sẽ xác định các api như findByLocation , calculateTotalUser và calculateSalary Controller : : EmployeeController
Lớp controller phía trên sẽ xác định các api cần thiết, các thông số đầu vào và trả về reponse chứa dữ liệu được wrap trong các DTO như EmployeeResult ,EmployeeDto dựa trên thông tin của mỗi API . Các DTO này sẽ được định nghĩa phía dưới. Lưu ý rằng ở đây controller sẽ gọi trực tiếp đến repository không thông qua lớp service do hiện tại chưa có nhiều logic phức tạp để xử lý và đây là những xử lý đơn giản. Tùy theo logic và các yêu cầu mà các bạn có thể dùng cho phù hợp trong từng dự án.EmployeeResult ,EmployeeDto dựa trên thông tin của mỗi API . Các DTO này sẽ được định nghĩa phía dưới. Lưu ý rằng ở đây controller sẽ gọi trực tiếp đến repository không thông qua lớp service do hiện tại chưa có nhiều logic phức tạp để xử lý và đây là những xử lý đơn giản. Tùy theo logic và các yêu cầu mà các bạn có thể dùng cho phù hợp trong từng dự án. Tạo một interface với tên là EmployeeRepository và EmployeeRepositoryCustom :EmployeeRepository và EmployeeRepositoryCustom : EmployeeRepository
EmployeeRepositoryCustom
Ở interface EmployeeRepositoryCustom chúng ta sẽ định cấu hình 3 method làEmployeeRepositoryCustom chúng ta sẽ định cấu hình 3 method là
EmployeeResult : :
EmployeeDto::
Tạo một lớp có tên là EmployeeRepositoryCustomImpl và thực hiện implement lại EmployeeRepositoryCustom như phía dưới:EmployeeRepositoryCustomImpl và thực hiện implement lại EmployeeRepositoryCustom như phía dưới:
Chúng ta sẽ thực hiện tính toán trong 3 method này sử dụng Aggregation dựa vào MongoTemplate. Vậy MongoTemplate là gì ? Lớp MongoTemplate, nằm trong gói org.springframework.data.document.mongodb, cung cấp các tính năng phong phú được thiết lập để tương tác với cơ sở dữ liệu. MongoTemplate cung cấp các method dùng để tạo, cập nhật, xóa và truy vấn cho các tài liệu MongoDB và cung cấp ánh xạ giữa các model và document. Đây là một lớp rất quan trọng trong việc thao tác với mongoDbAggregation dựa vào MongoTemplate. Vậy MongoTemplate là gì ? Tích hợp MongoTemplate vào EmployeeRepositoryCustom như sau:MongoTemplate vào EmployeeRepositoryCustom như sau: 0Đối với method fetchAllLastNameByLocation sẽ tiến hành xử lý như sau :fetchAllLastNameByLocation sẽ tiến hành xử lý như sau : 1Ở đây chúng ta có một lớp gọi là Criteria . Vậy Criteria là gì ? Đây là một lớp nằm trong package org.springframework.data.mongodb.core.query cung cấp nhiều phương thức để thực hiện truy vấn như WHERE , IS , LT, GT ... Nó cũng cung cấp một cách tuần tự việc thực hiện các truy vấn đó.Criteria . Vậy Criteria là gì ? Đây là một lớp nằm trong package org.springframework.data.mongodb.core.query cung cấp nhiều phương thức để thực hiện truy vấn như WHERE , IS , LT, GT ... Nó cũng cung cấp một cách tuần tự việc thực hiện các truy vấn đó. 2 dùng để tạo ra một criteria chứa điều kiện là các location trong database phải bằng với location từ param đã input vào từ Controller.criteria chứa điều kiện là các location trong database phải bằng với location từ param đã input vào từ Controller. 2Câu lệnh trên dùng để thực hiện tạo một Pipeline với toán tử match(criteria) với criteria đã định nghĩa phía trên và group theo lastName Kế đến câu lệnh : 3 3sử dụng mongoTemplate thao tác với database với aggregation đã định nghĩa phía trên, tên collections là Employees và trả về DTO là EmployeeDto.classaggregation đã định nghĩa phía trên, tên collections là Employees và trả về DTO là EmployeeDto.class Cuối cùng các câu lệnh như phía dưới thực hiện tạo ra 1 list các LastName với kiểu String, lấy kết quả từ results đã trả về , lặp và trả ra list các LastNameLastName với kiểu String, lấy kết quả từ results đã trả về , lặp và trả ra list các LastName Kết quả khi thực hiện gọi API trên bằng postman : Nhìn vào log trong spring chúng ta có thể thấy dễ dàng Spring Data Mongo đã thực hiện parse các lệnh chúng ta viết phía trên ra cú pháp MongoDb theo dạng Pipeline tuần tự : match -> group và thực hiện thao tác với MongoDb. 3Đối với method countTotalUserByLocation sẽ thực hiện tính toán như sau :countTotalUserByLocation sẽ thực hiện tính toán như sau : 4Cách thực hiện tương tự như ví dụ trên : Thực hiện dựa tạo một Criteria để thực hiện tìm kiếm dựa trên Location và dùng Aggregation để tạo các Pipeline để thực hiện tuần tự : Group location và thực hiện Count trên từng location đó. Tuy nhiên có 1 sự khác biệt là chúng ta sẽ dùng thêm một class là AggregationOperation để xử lý riêng cho từng toán tử như match , group ... Điều này khiến cho code chúng ta clear và dễ dàng maintain hơn. Ở đây chúng ta sẽ thực hiện lấy kết quả trực tiếp từ AggregationResults bằng method getMappedResults được định nghĩa sẵng trong api của mongoDb.Aggregation để tạo các Pipeline để thực hiện tuần tự : Group location và thực hiện Count trên từng location đó. Tuy nhiên có 1 sự khác biệt là chúng ta sẽ dùng thêm một class là AggregationOperation để xử lý riêng cho từng toán tử như match , group ... Điều này khiến cho code chúng ta clear và dễ dàng maintain hơn. Ở đây chúng ta sẽ thực hiện lấy kết quả trực tiếp từ AggregationResults bằng method getMappedResults được định nghĩa sẵng trong api của mongoDb. Kết quả khi thực hiện api : Kết quả hiển thị trên log spring : Kết quả hiển thị trên log spring : 5Cũng tương tự ví dụ phía trên, Spring Data MongoDb dựa trên các lệnh chúng ta đã viết thực hiện tạo các Pipeline theo tuần tự : $match theo location , $group theo location và tính tổng dựa vào $sum Đối với method calculateSalaryByAgeAndLocation thực hiện xử lý như sau:calculateSalaryByAgeAndLocation thực hiện xử lý như sau: 6Đối với method calculateSalaryByAgeAndLocation sẽ thực hiện tính toán dựa trên 2 đk: age và location . Criteria sẽ thực hiện lấy những đk với age lớn hơn hoặc bằng age được input vào và location bằng location từ param được input vào. Toán tử AggregationOperation sẽ thực hiện tương tự các trình tự như match, group by location , sum dựa vào salary và thực hiện tham chiếu đến từng lastName của Employee với lệnh push .calculateSalaryByAgeAndLocation sẽ thực hiện tính toán dựa trên 2 đk: age và location . Criteria sẽ thực hiện lấy những đk với age lớn hơn hoặc bằng age được input vào và location bằng location từ param được input vào. Toán tử AggregationOperation sẽ thực hiện tương tự các trình tự như match, group by location , sum dựa vào salary và thực hiện tham chiếu đến từng lastName của Employee với lệnh push . Kết quả : Log : 7Chúng ta có thể thấy : Spring Mongo Db đã parse các câu lệnh chúng ra viết ra dạng Pipeline , với các operator match : age lớn hơn hoặc bằng 23, location DN group theo location tính tổng dựa trên salary ... Thao tác MongoDb dựa trên Aggregation với CursorQua các ví dụ trên mongoTemplate cung cấp rất nhiều tiện ích cũng như method thao tác với mongoDb. Tuy nhiên có 1 hạn chế ở đây ? MongoTemplate không thể xử lý dữ liệu vượt quá 16MB cho một BSON. Vì sao như vậy ? Có rất nhiều lý do cho vấn đề này tuy nhiên nguyên nhân chính đó là MongoDb giới hạn một BSON không vượt quá 16MB để đảm bảo rằng nó không chiếm quá nhiều RAM của hệ thống khi xử lý và đảm bảo tốc độ truyền dữ liệu khi trả về ? Vậy nếu muốn sử lý dữ liệu vượt quá 16MB chúng ta phải làm thế nào ? Câu trả lời là dùng Cursor . Về Cursor là gì cũng như có những method nào các bạn có thể tham khảo tài liệu của MongoDb : https://docs.mongodb.com/manual/reference/method/js-cursor/?searchProperty=current&query=Cursor . Lưu ý là Cursor có các method riêng để xử lý, không thể dùng các method của collection để xử lý với Cursor. Lưu ý : Việc sử lý dựa vào Cursor chỉ áp dụng đối với lượng data vượt quá 16MB . Nếu data nhỏ hơn 16MB, hãy dùng mongoTemplate. Dùng Cursor sẽ khiến database của bạn chậm đi đáng kể dẫn đến hiệu suất không ổn định. Thao tác Cursor trong SpringTrước tiên chúng ta tạo 1 method trong lớp EmployeeController như thế này : 8Method này sẽ trả về 1 danh sách các lastName không trùng lặp Tiếp theo tạo một method có tên fetchAllLastNameByLocationUsedCursor trong Interface EmployeeRepositoryCustom và EmployeeRepositoryCustomImpl : EmployeeRepositoryCustom . . 4EmployeeRepositoryCustomImpl Tiến hành tìm tất cả các lastName dựa trên location bằng Cursor 9Thay vì dùng các lớp Employee để thực hiện ánh xạ, BasicDBObject thực hiện đọc trực tiếp vào Bson với tên trường là location, MongoClient xác định các kết nối như tên host, port, tên database. Phía trên tên host là localHost,port là 27017 và tên database là BaoTrung. Ở đây chúng ta dùng lớp DBCollection để get trực tiếp collection từ MongoClient . Collection được get lên ở đây là Employees. Thực hiện tạo một Set chứa các lastName mong muốn trả về. Cursor cursor = coll.find(query); dùng để xác định sẽ dùng cursor tìm kiếm dựa trên điều kiện đã định trước. Các method như cursor.hasNext() và cursor.next() dùng để thực hiện tìm kiếm tuần tự trong các bảng ghi. Cuối cùng lastNames.add(((String) instance.get("lastName"))); sẽ tìm kiếm ra các lastName phù hợp với location đã chỉ định parse về kiểu String và trả về kết quả.BasicDBObject thực hiện đọc trực tiếp vào Bson với tên trường là location, MongoClient xác định các kết nối như tên host, port, tên database. Phía trên tên host là localHost,port là 27017 và tên database là BaoTrung. Ở đây chúng ta dùng lớp DBCollection để get trực tiếp collection từ MongoClient . Collection được get lên ở đây là Employees. Thực hiện tạo một Set chứa các lastName mong muốn trả về. Cursor cursor = coll.find(query); dùng để xác định sẽ dùng cursor tìm kiếm dựa trên điều kiện đã định trước. Các method như cursor.hasNext() và cursor.next() dùng để thực hiện tìm kiếm tuần tự trong các bảng ghi. Cuối cùng lastNames.add(((String) instance.get("lastName"))); sẽ tìm kiếm ra các lastName phù hợp với location đã chỉ định parse về kiểu String và trả về kết quả. Kết quả khi thực hiện gọi api Log trong spring: 0Nhìn vào log có thể thấy lúc nào Spring Data MongoDb đã k còn parse ra các Pipeline nữa mà việc thực hiện hoàn toàn dựa vào Cursor thao tác trên bộ nhớ của mongoDB và trả về kết quả. Kết luậnQua bài viết trên , mình đã giới thiệu cách sử dụng Aggregation trong spring, cách sử dụng mongoTemplate và Cursor. Sự khác nhau và khi nào nên dùng Cursor khi nào không. Hy vọng sẽ giúp mọi người hiểu thêm về Aggregation và tích hợp vào dự án. Hẹn gặp lại mọi người ở các bài viết tiếp theoAggregation trong spring, cách sử dụng mongoTemplate và Cursor. Sự khác nhau và khi nào nên dùng Cursor khi nào không. Hy vọng sẽ giúp mọi người hiểu thêm về Aggregation và tích hợp vào dự án. Hẹn gặp lại mọi người ở các bài viết tiếp theo |