Thứ Ba, 25 tháng 8, 2020

Bài 3: Thực hiện việc truy vấn và trình bày dữ liệu với chức năng tìm kiếm với Spring 5 MVC

Xây dựng ứng dụng CRUD với Spring 5

Bài 3: Thực hiện việc truy vấn và trình bày dữ liệu với chức năng tìm kiếm

Mã Hoàng Nhật Phi

 

-         Mục đích: Tiếp nối series bài viết về xây dựng ứng dụng web dùng Spring 5 Framework, bài viết trước chúng ta đã trải nghiệm qua khái niệm dependencies, IoC và cách thức cấu hình Data Source để xây dựng nên chức năng đơn giản là Login và Logout. Trong bài viết này chúng ta sẽ thực hiện chức năng Search để truy vấn dữ liệu dưới DB sau đó chuyển đổi thành dạng format để đưa về client trình bày dữ liệu. Quá trình lấy dữ liệu sẽ thực hiện gọi WEB API sử dụng RESTful Web Services và gọi thực thi chức năng behind the scenes.

-         Yêu cầu kiến thức cơ bản:

o        Nắm vững và sử dụng các kỹ năng để về việc xây dựng hoàn tất ứng dụng với mô hình MVC2

o        Nắm vững các khái niệm về ngôn ngữ lập trình Java, lập trình thao tác hướng đối tượng.

o        Nắm vững các khái niệm về lập trình web sử dụng J2EE hay JavaEE với các kiến thức về Servlet, JSP. http://www.kieutrongkhanh.net/search/label/Servlet%26JSP

o        Tìm hiểu và nắm vững các khái niệm và ứng dụng XMLHttpRequest trong việc gọi ứng dụng hạn chế postback ở client (behind the scene) tại địa chỉ https://www.w3schools.com/xml/xml_http.asp http://www.kieutrongkhanh.net/2016/12/du-lieu-uoc-request-tu-server-va-chuyen.html

o        Tìm hiểu và nắm được các khái niệm cùng với các kỹ năng sử dụng RESTful Web Services, tham khảo tại địa chỉ http://www.kieutrongkhanh.net/2016/08/xay-dung-ung-dung-ap-dung-restful-web.html  hay http://www.kieutrongkhanh.net/2016/08/gioi-thieu-ve-restful-web-services-cong.html

o        Đã hoàn tất bài xây dựng chức năng Login sử dụng Spring MVC5 tại địa chỉ http://www.kieutrongkhanh.net/2020/08/bai-2-cau-hinh-ket-noi-csdl-su-dung.html

-         Tool sử dụng:

o   Netbeans 8.x

o   JDK 8 update 66

o   DBMS: SQL Server từ 2005 đến 2019, PostgreSQL 10 (Đón xem nội dung bổ sung trong loạt bài viết tiếp theo)

                                           I.                        Giới thiệu

-         Sau bài viết số 2, chúng ta đã hiểu cơ bản về mô hình hoạt động của Spring 5 MVC Framework và cách vận dụng kết hợp việc cấu hình để tạo ra chức năng đơn giản Login có truy vấn dữ liệu từ CSDL. Trong bài viết này, chúng ta tiếp tục hoàn thiện chức năng tiếp theo để truy vấn dữ liệu từ CSDL sau đó chuyển sang dạng format Json để truyền về phía client sử dụng RESTful Web Services để gọi behind the scene để trình bày kết quả ở phía client.

                                        II.                        Nội dung

2.      Thực hiện tính năng search

Chúng ta thực hiện thêm cấu hình trong file mvc-config.xml với mục tiêu định vị cho Spring framework biết vị trí đặt file cấu hình được xác định ở attribute location khi có request tới một resources xuất hiện trong attribute của mapping như sau:

Spring framework sẽ hỗ trợ tìm kiếm các thành phần hỗ trợ lấy dữ liệu và trình bày dữ liệu từ client đến phía server thông qua JavaScript. Thư mục resources thường nằm tại thư mục webapp của ứng dụng web.

Lưu ý: giá trị điền vào trong mapping là resources/**

Chúng ta thực hiện bổ sung form thực hiện việc chức năng search cùng với đường dẫn đến file JavaScript để xử lý và trình bày giao diện ở client sử dụng DHTML vào trang search.jsp

-                      Với code ở trên chúng ta đang sử dụng JavaScript để đón nhận và trình bày dữ liệu ở phía client và chúng ta đang chỉ đường dẫn đến tập tin JavaScript để hỗ trợ việc thực hiện xử lý

-                      Ở đây, chúng ta sẽ sử dụng một framework để map dữ liệu giữa các object gọi là mapstruct.

o    Mapstruct sẽ generate ra các class mapper làm nhiệm vụ deep copy (chứa code để lấy dữ liệu từ field của một object gán vào field của một object khác).

o   Mục đích của việc sử dụng mapstruct cho mapping data giữa các object với mục tiêu tách biệt các lớp xử lý như controller, service, repository.

o   Việc tách biệt này nhằm tạo tính loose coupling giữa các lớp xử lý với nhau. Điều này đem lại cho chúng ta rất nhiều lợi ích khi có sự thay đổi cấu trúc dữ liệu ở một lớp nào đó thì sẽ hạn chế ảnh hưởng đến các thành phần khác trong ứng dụng.

o   Chúng ta thực hiện tạo các lớp tương ứng với từng lớp data như sau:

§  Controller – ResponseModel, RequestForm

§  Service – DTO

§  Repository – Entity (Domain)

o   Đầu tiên, chúng ta tạo ra các common mapper là DTOGenericMapperResponseModelGenericMapper để định nghĩa các phương thức mapping dữ liệu dùng chung cho cả project của chúng ta.

§  Bắt đầu với interface DTOGenericMapper

§  Việc tạo ra DTOGenericMapper để thực hiện deep copy dữ liệu từ entity sang DTO. Việc copy này tạo ra tính khả chuyển (dễ dàng thay đổi về tên trường, kiểu dữ liệu của một class) giúp quá trình phát triển ứng dụng thuận tiện hơn. Sự hỗ trợ này sẽ tách biệt sự móc nối về giữa giữa hai tầng là Repository (Entity) và Service (DTO).

§  S, D ở đây là tên của 2 generic class đại diện cho 2 class cụ thể sẽ được người tạo ra thành phần mapping dữ liệu.

·        S: Source class

·        D: Destination class

o   Như chúng ta thấy trong ví dụ trên, khi hiện thực hóa mapper cho DTO cho 2 class là Registration và RegistrationDTO, ta sẽ có S là Registration và D là RegistrationDTO. Do chúng ta đã extend từ interface GenericMapper nên các mapper mà chúng ta vừa tạo có các hàm từ generic mapper. Việc tạo ra các generic class cho project sẽ quy định các common method để đồng nhất về signature các chức năng xử lý có tầng suất xuất hiện cao trong project nhầm thống nhất, quy định tên method cho project và tránh việc khai báo trùng lặp các method tương tự nhau.

o   Lần lượt chúng ta có các phương thức sau:

§  D toDTO(S entity)

§  S toEntity(D dto)

§  List<D> toDTO(List<S> entities)

§  List<S> toEntity(List<D> dtos)

o   Chúng ta dễ dàng nhận thấy có hai cặp phương thức để mapping từ entity sang DTO cho các class mapping trong project.

o   Chúng ta có thể minh họa cho việc mapping trên như hình bên dưới cùng với interface được khai báo

o   Chúng ta cũng có hai cặp hàm để mapping từ DTO sang ResponseModel cho các class mapping trong project.

o   Nội dung trên cũng tương tự như tác dụng của DTOGenericMapper là tách biệt móc nối giữa thành phần dữ liệu giữa DTO và ResponseModel tránh những khó khăn khi muốn thay đổi kiểu dữ liệu hoặc tên trường.

o   Chúng ta có mô hình mapping dữ liệu tương tự như sau

 

o   Như vậy việc dùng các generic mapper để làm common interface quy định các phương thức mapping dữ liệu chung cho cả project sẽ giúp chúng ta định danh về cách đặt tên hàm cũng như tránh việc khai báo lại ở các mapper cụ thể.

o   Việc tách biệt các data model khác nhau tách biệt ở các tầng như entity (Repository), DTO (Service), ResponseModel (Controller) để tránh việc khó khăn khi chuyển đổi kiểu dữ liệu và tên trường khi cần thiết trong quá trình phát triển ứng dụng.

o   Chúng ta thực hiện tạo ra các mapper căn cứ trên nội dung bài thực hiện dựa trên các mapper tổng quan như sau

§  Để hỗ trợ xử lý trước khi tạo ra DTO Mapper, chúng ta cần tạo DTO như sau:

§  Lưu ý, tên field phải tương ứng với tên field được đặt trong class – entity Registration không thôi dữ liệu sẽ không mapping tự động được. Cuối bài này, chúng ta sẽ thấy được các case báo lỗi khi việc mapping không tương đương với entity đã tạo ra ở bài Login

§  Mọi thứ đã sẵn sàng để implement RegistrationDTOMapper

·        Để có mapstruct, chúng ta cần add dependencies như sau vào file pom.xml như sau

o   Thực hiện lệnh clean-install để đem thư viện về

·        Chúng ta bổ sung thêm config cho annotationProcessorPath để trong quá trình compile mapstruct, tool IDE sẽ generate ra các class mapper

·        Ở đây trong field componentModel của annotation Mapper, chúng ta cần thêm giá trị là spring để mapstruct biết được chúng ta đang tích hợp vào ứng dụng spring để tạo ra spring bean cần thiết.

·        Chúng ta thực hiện clean – install, sau đó, tại mục Generated Sources (annotations), một class được mapstruct phát sinh

-         

-         

§  Tương tự như thế chúng ta implement đối tượng Response Model cụ thể cho bài chúng ta

·        Chúng ta implement class RegistrationResponseModel (thực chất là một bean class)

·        Lưu ý một lần nữa, tên field phải tương ứng với tên field được đặt trong class – entity Registration không thôi dữ liệu sẽ không mapping tự động được. Cuối bài này, chúng ta sẽ thấy được các case báo lỗi khi việc mapping không tương đương với entity đã tạo ra ở bài Login

·        Chúng ta tiếp tục bổ sung interface hiện thực RegistrationResponseModelMapper từ ResponseModelGenericMapper

o   Việc bổ sung annotation Mapper với giá trị spring tương tự như DTOMapper

o   Chúng ta thực hiện lại lệnh clean – install để mapstruct phát sinh cho chúng ta những class Imp như DTO

§  Việc chúng ta khai báo tên method ở repository và giao việc hiện thực hóa cách lấy và lưu trữ dữ liệu cho Spring framework sẽ giúp chúng ta abstract được thành phần DAO. Như vậy, chúng ta chỉ cần quan tâm tới business logic trong ứng dụng, từ đó giảm thiểu thời gian hiện thực hóa các hàm lấy hoặc lưu dữ liệu mà tập trung hơn vào business logic của ứng dụng. Ở đây ta đề cập tới boilerplate code để hạn chế lỗi (bug) khi implement các phương thức không quá phức tạp hoặc chứa business chuyên biệt.

§  Bên cạnh đó, Spring Data JPA cung cấp cho chúng ta quy tắc để đặt generate ra câu sql tương ứng việc này giúp ta vừa abstract được thành phần DAO vừa hỗ trợ cho các xử lý có ngữ nghĩa tương ứng.

o   Để tìm hiểu nhiều hơn về tính khả chuyển của thành phần data trong ứng dụng khi sử dụng mapstruct để mapping dữ liệu, chúng ta sẽ nghiên cứu trong các loạt bài nâng cao sử dụng mapstruct để mapping dữ liệu trong loạt bài Spring Framework tiếp theo.

Chúng ta bắt đầu bổ sung controller cung cấp API nhằm tại ra services sử dụng RESTful (địa chỉ tham khảo http://www.kieutrongkhanh.net/2016/08/xay-dung-ung-dung-ap-dung-restful-web.html  hay http://www.kieutrongkhanh.net/2016/08/gioi-thieu-ve-restful-web-services-cong.html) với tên class RegistrationRestController vào package controller. Để bắt đầu thực hiện công việc này, chúng ta cần bổ sung các thành phần hỗ trợ trước khi tạo ra controller

-          Chúng ta bổ sung thêm phương thức getByLastnameContainingIgnoreCase vào Repository với mục tiêu thực hiện tìm kiếm dữ liệu theo field lastname

-          Chúng ta thêm hàm searchByLastName vào service để thực hiện cung cấp chức năng search để truy cập được dữ liệu trực tiếp từ DB sử dụng JPA

-          Chúng ta đã có thể implement RegistrationController như sau:

-                      Sau khi hoàn thành phần lấy dữ liệu và chuẩn bị dữ liệu ở phần backend, chúng ta cần chuyển dữ liệu truy vấn để trình bày kết quả ở client.

o   Chúng ta sẽ dùng XMLHttpRequest để giao tiếp với phía backend. Sau khi nhận được dữ liệu từ backend trả về ta sẽ dùng JavaScript để hiện thị dữ liệu.

o   Chúng ta tạo thư mục resources trong thư mục webapp trên project. Sau đó, chúng ta tạo tiếp thư mục js trong thư mục resources. Cuối cùng, chúng ta tạo  tập tin main.js ở thư mục js.

o   Chúng ta thực hiện tạo chức năng search sử dụng XMLHttpRequest để gọi RESTful Service từ server API thông qua việc lấy giá trị search người dùng đã nhập trên form truyền về server để thực thi và đón nhận kết quả trở về behind the scenes

o   Sau khi có kết quả trả về, chúng ta đã chuyển đổi từ kiểu JSON thành dạng text, chúng ta cần thực hiện render nơi chứa dữ liệu, cụ thể là tạo ra table rồi đổ dữ liệu vào trong table đó.

o   Chúng ta thực hiện bổ sung 02 function của JavaScript để mục tiêu tạo ra table và đổ dữ liệu từ việc thực thi chức năng search vào table vừa tạo ra vào trong tập tin main.js

§  Function tạo ra bảng table với tiêu đề cột renderHeaderRegistrationRows

§  Chúng ta tiếp tục tạo ra function đổ dữ liệu đã xử lý sau khi gọi chức năng search vào trong table có tên là renderRegistrationRows

-          Chúng ta thực hiện clean – install ứng dụng

-          Sau đó, thực hiện deploy để chạy test kết quả

-          Lỗi này phát sinh tương tự đã đề cập ở bài trước là dependencies cùng với IoC Container theo nghĩa phải xác định việc lookup Controller để tiêm các thành phần ModelMapper vào

-          Chúng ta khắc phục lỗi này bằng cách điều chỉnh việc look up trên toàn bộ gói package cha chứa các gói bên trong vào tập tin application-config.xml như sau

-          Việc này sẽ hỗ trợ chúng ta look up các đối tượng được đối tượng khác tiêm vào một cách dễ dàng

-          Chúng ta clean – install lại. Sau đó deploy chạy lại để test thử.

-          Kết quả chúng ta nhận được có thể là lỗi trên browser như hình bên dưới

-          Hay là giao diện search vẫn giữ nguyên không thay đổi và dưới sever chúng ta có lỗi tương tự như sau

-          Lỗi này là do dữ liệu của chúng ta chưa thể chuyển đổi từ dạng object truy vấn từ DB trở thành JSON để chuyển đi đến client.

-          Chúng ta khắc phục điều này bằng cách bổ sung thêm hai dependency vào file pom.xml như sau:

-          Khi bổ sung dependencies này xong, chúng ta chạy lại lệnh clean – install để hỗ trợ converter cho Spring framework serialize object thành JSON.

-          Chúng ta thực hiện deploy ứng dụng và thực hiện chạy đến tính năng search sau khi login như các hình bên dưới

Bên cạnh việc hoàn tất chức năng search sau khi thực thi các bước trên, chúng ta cần chia sẻ các kinh nghiệm liên quan đến các vấn đề lỗi xảy ra trong lúc chúng ta thực hiện một chức năng để có thể thực hiện khắc phục một cách nhanh nhất và biết lỗi ở đâu mà khắc phục

o   Case 1: Với entity Registration, giả sử chúng ta mapping cột lastname dưới DB thành property fullName như trong hình bên dưới mà không sửa các thành phần khác trong các bước trước đã làm khi chạy xong chức năng search

….

§  Chúng ta thực hiện clean – install và deploy ứng dụng. Lỗi phát sinh tại thời điểm deploy như sau

                    

§  Lỗi trên phát sinh vì việc mapping dựa trên property trong entity và hàm tương ứng là getByLastName… trong khi property của chúng ta là fullName thì tên tương ứng của nó phải là FullName. Chúng ta thực hiện cập nhật code với giả sử cập nhật entity như bước trên trong RegistrationRepository

§  Và trong phương thức searchByLastname của class RegistrationService vì phương thức gọi phương thức getBy…

§  Chúng ta thực hiện clean – install, deploy và test chức năng search thì kết quả chúng ta nhận được như sau

§  Lỗi này phát sinh là do chúng ta chưa mapping đúng dữ liệu đã đề cập ở phần note là tên field của RegistrationResponseModel phải tương ứng với property của entity Registration vì theo cách suy nghĩ logic thì ResponseModel được sử dụng kết hợp với ResponseModelMapper là trả về kết quả để đưa đến client. Chúng ta thực hiện cập nhật code của RegistrationResponseModel như sau

§  Đồng thời cập nhật luôn field truy vấn trong JavaScript ở tập tin main,js

§  Chúng ta thực hiện clean – install, deploy lại và test lại chức năng search. Chúng ta nhận được kết quả như sau

§  Lỗi này phát sinh vì dù sao đi nữa việc mapping dữ liệu cũng phải qua DTO rồi mới đến ResponseModel. Lần nữa nội dung note trong các phần để hỗ trợ chúng ta xác định lấy field từ đâu không phải gán vô tội vạ vì chúng có sự mapping lẫn nhau. Chúng ta thực hiện chỉnh sửa code của RegistrationDTO theo điều chỉnh giả định

§  Chúng ta thực hiện clean – install, deploy lại và test lại kết quả search. Kết quả chúng ta nhận được y chang như khi thành công ban đầu trước khi thay đổi mapping dữ liệu

o   Case 2: Chúng ta viết service mà không mapping services để truy cập, cụ thể ở đây chúng ta quên không mapping service name khi implement hàm search trong RestController. Giả sử chúng ta bỏ đi dòng @GetMapping trong code

§  Chúng ta thực hiện clean-install, deploy và thực thi đến màn hình search. Khi nhấn nút search, màn hình search vẫn đứng yên. Chúng ta xem dưới server sẽ có lỗi như sau

§  Lỗi này là do service chưa được map hay gọi sai tên dẫn đến việc gọi theo URL bị sai tên resource và tất yếu là 404

o   Case 3: Khi chúng ta khai báo đối tượng muốn injection theo IoC nhưng quên gán thông qua constructor hay các hàm set thì vấn đề sẽ phát sinh tại thời điểm deploy. Ở đây, chúng ta giả sử chúng ta đã khai báo DTOMapper những chưa đưa vào constructor

§  Thực hiện clean – install, deploy chúng ta sẽ thấy lỗi như sau ở server

§  Chúng ta chỉ cần bổ sung lại đúng nội dung ban đầu và clean – install, deploy và thực thi lại là ok

Chúc mừng quí vị, chúng ta vừa hoàn tất việc xây dựng chức năng search cùng với các skill mapping để lấy dữ liệu xử lý chuyển đổi thành Services API để consume ở client. Bên cạnh đó, chúng ta đã cùng nhau chia sẻ các tình huống cần xử lý trong lúc làm ứng dụng. Hy vọng nội dung này sẽ hỗ trợ quí vị trong việc sử dụng Spring Framework trong dựa trên mô hình MVC2

 

Rất mong quí vị góp ý về nội dung loạt bài viết này. Hẹn gặp lại quí vị ở các bài viết sau về các nội dung liên quan đến việc thêm xóa sửa (CUD) sử dụng Spring 5 Framework.

 

Không có nhận xét nào:

Đăng nhận xét