Thứ Năm, 10 tháng 12, 2020

Bài 5: Thực hiện chức năng delete và kiểm tra các ràng buộc về dữ liệu khi thực hiện chức năng register

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

Bài 5: Thực hiện chức năng delete
và kiểm tra các ràng buộc về dữ liệu khi thực hiện chức năng register

Mã Hoàng Nhật Phi, Trương Trần Tiến

 

-         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 đã hiện thực hóa chức năng Update sử dụng XMLHTTPRequest để gọi RESTful web service. Qua đó, chúng ta đã tìm hiểu qua cách thức bắt exception sử dụng khái niệm AOP. Đồng thời, chúng ta cũng được giới thiệu về cách ràng buộc dữ liệu thông qua các annotation có sẵn. Trong bài viết này, chúng ta sẽ thực hiện chức năng delete, create (register) và thực hiện ràng buộc dữ liệu bằng các validator, annotation riêng theo cách chúng ta mong muốn dữ liệu đảm bảo các toàn vẹn theo qui định theo yêu cầu trong việc tổ chức cấu trúc.

-         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 các bài xây dựng chức năng liên quan đến Spring MVC5 tại địa chỉ http://www.kieutrongkhanh.net/search/label/Spring5

-         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)

Giới thiệu

-         Sau bài viết số 4 (tham khảo tại địa chỉ http://www.kieutrongkhanh.net/2020/11/bai-4-thuc-hien-viec-update-du-lieu-va.html ), chúng ta đã nắm cơ bản cách tương tác giữa client – server thông qua việc tạo RESTful API trong việc update dữ liệu. Chúng ta đã nắm rõ được cách xử lý exception. Chúng ta đã biết cách ràng buộc dữ liệu thông qua các annotation và validator mà Hibernate đã cung cấp. Tuy nhiên, Hibernate chỉ validate cho chúng ta những ràng buộc dữ liệu cơ bản và đơn giản. Để có thể validate dữ liệu với các điều kiện phức tạp hơn, chúng ta cần phải có khả năng tự tạo validator riêng hoặc thậm chí tạo annotation riêng. Chúng ta sẽ thực hiện 2 điều đó trong bài viết này.

 

Nội dung

1.      Thực hiện chức năng delete

                                 a.      Bổ sung phương thức thực hiện xóa dữ liệu theo username trong Repository

-          Chúng ta bổ sung phương thức deleteByUsername vào repository, ở đây spring sẽ thực hiện generate cho chúng ta.

-          Phương thức deleteByUsername nhận vào tham số là khóa chính và trả về là số lượng row dữ liệu được affected ở database

                                 b.      Bổ sung phương thức delete vào trong Service

                                 c.      Bổ sung Mapping trong Rest Controller (cụ thể ở đây là class RegistrationRestController)

-                    Việc cài đặt của phương thức delete trong controller ở trên tương tự như chức năng Update, nếu chúng ta delete thành công sẽ trả về response message với status 200 OK.

                                 d.      Cập nhật nội dung trong tập tin main.js

-                   Cập nhật header của bảng

-                   Add delete link vào mỗi hàng

-                   Bổ sung function delete vào tập tin main.js

                                 e.      Chúng ta thực hiện clean-install và deploy sử dụng maven. Thực hiện chạy thử để kiểm tra kết quả

-                   Click vào nút X, chúng ta sẽ nhận được kết quả như sau

-                   Lưyu ý:

o   Nếu trong quá trình thực thi sau khi deploy phát sinh lỗi như sau trong server

o   Và trong cửa sổ log xuất hiện tương tự như sau

….

o   Đây là lỗi phát sinh do yêu cầu khi thực hiện các phương thức dưới DB thông qua Entity Manager phải bắt buộc có transaction

o   Chúng ta thực hiện modify nội dung đảm bảo transaction vào nơi thực hiện gọi Entity Manager. Việc define Transaction sẽ phải thực hiện ở class Repository trực tiếp trên phương thức triệu gọi, cụ thể như sau

2.      Thực hiện chức năng register

                                    a.            Tạo register form với tên register.jsp vào thư mục WEB-INF/view

-                      Hàm register() trong code để thực thi tác vụ trên trang jsp, chúng ta sẽ được bổ sung sau trong tập tin register.js (nội dung này sẽ được thể hiện trong các phần tiếp theo)

                                    b.            Bổ sung link ở trang login và invalid để truy cập trang register và tạo mapping ở MainController để truy cập register.jsp

-                    Bổ sung link ở trang invalid.jsp và login.jsp

Graphical user interface, text, application, email

Description automatically generated

Graphical user interface, text, application, email

Description automatically generated

-                    Chúng ta tiến hành tạo thêm request mapping trong class MainController để có thể truy cập được register.jsp

Graphical user interface, text, application, email

Description automatically generated

                                    c.            Tạo class RegisterForm

-                      Tương tự như chức năng update, chúng ta cần phải xây dựng một Model để mapping dữ liệu từ request message. Class RegisterForm được đặt ở package form theo qui chuẩn prefix chúng ta đã cấu hình từ bài số 2 sẽ chứa các thông tin cần thiết từ request.

-                      Chúng ta định nghĩa các thuôc tính tương tự như trên form của trang jsp mà chúng ta đã định nghĩa ở trên rồi thực hiện phát sinh các phương thức getter và setter theo qui chuẩn của JavaBeans

 

-                      Lưu ý: các giá trị property mà mapping với xuống database phải được đồng bộ từ class DTO. Cụ thể ở đâylà các tên field username, password, lastname phải được copy từ class DTO

                                    d.            Tạo mapper từ RegisterForm sang RegistrationDTO

-                   Chúng ta thực hiện tạo interface model mapper với tên gọi RegisterFormMapper kế thừa từ Mapper tổng quát đã được xây dựng trong các bài trước. Chúng ta đặt interface này trong package mapper hay modelmapper cũng được

-                   Việc cài đặt ứng dụng có hỗ trợ của Spring container đòi hỏi việc chính xác trong mapping dữ liệu giữa các thành phần DTO và Form. Nói một cách tổng quát, các giá trị tên field – property được mapping phải được copy từ DTO sang để từ đó đảm bảo dữ liệu được mapping đúng và khi việc injection tự động sẽ đảm bảo dữ liệu được đưa đến các field phù hợp mà không cần gán một cách tường minh tăng tính flexibility khi ứng dụng framework

                                    e.            Update class RegistrationService

-                      Chúng ta mở class RegistrationService để thực hiện bổ sung các phương thức phục vụ trong chức năng Register. Cụ thể ở đây là phương thức tạo createUser để thực hiện tạo mới account và phương thức isUsernameExisted để kiểm tra username đã tồn tại dưới DB hay không

                                      f.            Tạo custom Validator cho việc ràng buộc dữ liệu

Chúng ta thực hiện tạo class để thực hiện bắt các ràng buộc về dữ liệu. Class RegisterValidator được đặt trong package validator với prefix từ khi tạo project

-          Chúng ta chọn Validator implementation từ gói org.springframework.validation.Validator

-          Chúng ta chọn phát sinh các phương thức abstract cần implement và thực hiện code như bên dưới

-                      Chúng ta  đã tạo ra một class validator để validate dữ liệu trong form nhằm đảm bảo ràng buộc dữ liệu trước khi đưa vào database. Class validator này sẽ sử dụng class Errors để lưu trữ các lỗi trong quá trình validate.

-                      Ở đây, chúng ta implement hai phương thức là: supportsvalidate.

o        supports: phương thức cho kiểm tra kiểu dữ liệu ở controller có được hỗ trợ validate.

o        validate: chứa xử lý kiểm duyệt dữ liệu nhập của người dùng.

-                      Chúng ta đã sử dụng @Component(“registerValidator”) để báo cho Spring biết đây là một bean có tên riêng biệt để Spring dễ phân biệt trong quá trình Dependency Injection. Chúng ta sẽ thấy mục đích của việc này trong phần tiếp theo.

-                      Class Errors cung cấp cho chúng ta các hàm rejectValue để khai báo lỗi. Hàm rejectValue nhận vào 3 tham số: field, errorCode, defaultMessage

o        Field: khai báo field nào của object gây ra lỗi này.

o        errorCode: khai báo lỗi cho từng field. Lấy ví dụ trong đoạn code trên thì username field sẽ có 2 lỗi tương ứng với 2 errorCode là username.lengthusername.existed

o        defaultMessage: đây là default message để ta hiện thị lỗi. Default message có thể null

-                      FielderrorCode cho phép chúng ta xác định các lỗi của object trong lúc bắt execption hoặc khi chúng ta tạo các Message riêng cho các lỗi. Bài viết sau chúng ta sẽ hiện thực việc tự tạo custom Message cho các lỗi này.

-                      Lưu ý: trong Spring có 3 cách để khai báo @Autowired để Spring có thể thực hiện Dependency Injection – phụ thuộc vào thời điểm chúng ta muốn Spring inject bean.

o   Sử dụng @Autowired ở trước constructor. Trong lúc khởi tạo instance, Spring sẽ đồng thời inject luôn bean.

o   Sử dụng @Autowired ở trước trực tiếp các field. Tương tự như khi sử dụng trước constructor

o   Sử dụng @Autowired ở trước các phương thức Setter. Sau khi đã khởi tạo được object thì Spring mới sử dụng setter để inject bean.

-                   Một trong những ví dụ trong việc lựa chọn cách sử dụng @Autowired là để giải quyết vấn đề “phụ thuộc vòng”. Nếu như class A sử dụng @autowired class B ở constructor và class B cũng sử dụng @Autowired class A ở constructor thì khi đó cả 2 class đều sẽ không bao giờ được tạo và Spring sẽ báo lỗi.

 

-                   Chúng ta xử lý lỗi này bằng cách thiết lập @Autowire trước phương thức setter ở class A

                                    g.            Thêm bean vào trong Rest Controller

-                   Chúng ta thực hiện bổ sung các nội dung cấu hình ở trên vào Rest Controller, cụ thể ở đây là mapper và validator   

Graphical user interface, text, application, email

Description automatically generated

-                      Tại Rest Controller, chúng ta sẽ thêm một bean validator vào và chỉ định tên bean name đã đặt tên trước đó để thực hiện validate cho form đăng ký là registerValdiator.. Bởi vì Validator là một interface có thể có rất nhiều class implement lại, nên trong lúc runtime Spring Container không thể chọn được implementation class nào để inject vào trong Rest Controller. Chúng ta cần phải tường minh chỉ định class thông qua @Qualifier annotation (sử dụng package org.springframework.beans.factory.annotation.Qualifier).

-                      Lưu ý: Lỗi thường gặp ở đây trong việc injection, chúng ta quên gán giá trị sau khi truyền tham số vào constructor thì việc injection không được container thực hiện dẫn đến các phương thức – behavior khi được gọi luôn thông báo lỗi là NullPointerException khi được triệu gọi

                                    h.            Binding Custom Validator với Object cần validate trong RestControntroller

-          Trong bài viết trước, chúng ta biết rằng khi sử dụng annotation @Valid, Spring sẽ đưa object đến cho validator thích hợp để xử lý. Vì chúng ta tự tạo một custom validator nên chúng cần khai báo nó thông qua sử dụng WebDataBinder.

-          @InitBider dùng để xác định những hàm khởi tạo class WebDataBinder. Class này đóng vai trò như một lớp preprocess những request được gửi tới từ phía client. Chúng ta có thể sử dụng WebDataBinder để format dữ liệu hoặc validate dữ liệu.

Image for post

Graphical user interface, text, application, email

Description automatically generated

-          Lưu ý

o   Nếu chúng ta chỉ @InitBider mà không sử dụng thêm value thì validator của chúng ta sẽ áp dụng cho mọi object có annotation @Valid. Nhưng Validator của chúng ta chỉ áp dụng cho riêng class RegisterForm do đó nếu áp dụng cho các class khác sẽ bị lỗi “IllegalStateException: Invalid target for Validator

o   Khi sử dụng @InitBider với value thì value phải là tên của class mà chúng áp dụng validator và viết thường chữ cái đầu, cụ thể ở đây chúng ta đã cài đặt class RegisterForm nên giá trị truyền vào là registerForm.

                                      i.            Tạo request mapping cho việc Register trong Rest Controller

-                      Chúng ta thực hiện tạo RequestMapping cho phương thức POST. Tương tự như chức năng Update ở bài trước, phương thức register sẽ nhận vào một object RegisterForm. Object này đã được Spring convert từ Request Message thông qua @RequestBody và Object này sẽ được validate thông qua @Valid.

-                      Nếu validate thành công sẽ gửi response 200 OK về phía người dùng

-                      Nếu validate không thành công sẽ throw lỗi MethodArgumentNotValidException. Cách bắt lỗi này đã được trình bày ở bài trước trong phần “Tạo ExceptionHandlerController”.

                                      j.            Kiểm tra cấu trúc error object khi ràng buộc dữ liệu không thành công

Trong bài viết trước chúng ta đã biết được rằng class ExceptionHandlerController là nơi để xử lý các exception được ném ra trong quá trình xử lý ở các Controller. Trong trường hợp bài viết, chúng ta quan tâm đến cách xử lý MethodArgumentNotValidException khi dữ liệu không đáp ứng được yêu cầu ràng buộc.

-                   Chúng ta sử dụng Map để lưu trữ các lỗi, khi tạo Response Message trả về phía client, Spring sẽ tự dộng chuyển đổi dữ liệu kiểu Map thành chuỗi JSON.

-                   Trong bài viết thì chuỗi JSON trả về của chúng ta sẽ có dạng như sau

-                   Các key trong node “errors” của chuỗi JSON sẽ trùng với Field của error trong class RegisterValidator khi chúng ta rejectValue

A picture containing application

Description automatically generated

-                   Hiểu rõ vể cấu trúc của ResponseMessage trả về sẽ giúp cho chúng ta dễ hơn trong việc viết code lấy dữ liệu để báo lỗi cho người dùng

                                    k.            Tạo register.js để xử lý register

-                    Vì file main.js chỉ dùng khi người dùng đã đăng nhập thành công nên chúng ta cần tạo mới file register.js để chuyên biệt xử lý chức năng register.

-                   Chúng ta thực hiện tạo hàm register() khi người dùng bấm submit. Khi register thành công thì sẽ chuyển về trang Login. Nếu nhận được status code 400 thì chúng ta xóa các lỗi trước và trình bày lỗi mới được cập nhật.

-                   Lưu ý:

o   Các nội dung trong khung tô đỏ phải được copy từ các field – property từ class RegisterForm

o   Hàm JSON.stringify giúp convert một JavaScript Object thành chuỗi JSON. Trong trường hợp của chúng ta là biến Object formField thành JSON. Spring sẽ tự động gán các giá trị trong chuỗi JSON cho các field trong class RegisterForm.

-      

-                    Chúng ta thực hiện tạo hàm display error để trình bày lỗi nếu có lỗi phát sinh

-                    Ở đây, chúng ta cần lưu ý cấu trúc error được phát sinh từ validator như sau

o   Lưu ý: Các giá trị trong các khung tô đỏ phải được copy chính xác từ giá trị của tham số đầu tiên – field trong phương thức rejectValue trong class RegisterValidator

-                   Chúng ta tiếp tục thực hiện tạo hàm xóa error mỗi khi người dùng submit

-                   Chúng ta thực hiện cập nhập css trong tập tin main.css

                                      l.            Kiểm tra kết quả

-                   Khi không nhập giá trị gì cả

-                   Khi giá trị username bị trùng hay confirm password không tương thích với password

3.      Sử dụng annotation để binding validator

-                   Trong chức năng Register ở phần 2, chúng ta đã biết cách binding Validator cho Object mà chúng ta cần validate thông qua @InitBinder. Trong phần này, chúng ta sẽ thực hiện binding Validator sử dụng annotation trực tiếp trên class RegisterForm

-                   Ngoài các ràng buộc về độ dài, chức năng Register của ta còn có 2 ràng buộc khác là:

o   Kiểm tra confirm password có trùng với password

o   Kiểm tra xem username có tồn tại hay chưa

-                   Vì vậy chúng ta sẽ tạo 2 annotation và 2 class Validator tương ứng cho 2 ràng buộc trên,

                                    a.               Tạo Annotation interface ConfirmPasswordNotMatch trong package validator.annotation

-                   Class ConfirmPasswordValidator sẽ được tạo trong bước b

-                   Để khai báo một Annotation, chúng ta khai báo như interface nhưng có @ ở trước chữ interface.

-                   Java cung cấp cho chúng các Annotation khác để chúng IDE hoặc JVM có thể xác định được chỗ đặt hay thời gian sống của Annotation.

o   @Documented: Đặt annotation vào trong javadoc

o   @Constraint: Đây là annotation để đánh dấu validator nào sẽ xử lý annotation này

§  validatedBy: đây là value để dánh dấu class nào sẽ thực hiện việc kiểm tra ràng buộc cho annotation này. Trong bài viết thì class ConfirmPasswordValidator sẽ được hiện thực sau.

o   @Target: nơi mà annotation có thể được đặt

§  ElementType.TYPE: được đặt trước class, interface, enum. VD: @Controller, @RestController

§  ElementType.FIELD: được đặt trước các biến instance. VD: @Size, @NotNull

§  ElementType.METHOD: được đặt ở trước các method. VD: @RequestMapping

§  ElementType.PARAMETER: được đặt trước các parameter trong method. VD: @RequestBody, @Valid

§  ElementType.CONSTRUCTOR: được đặt trước constructor

§  ElementType.LOCAL_VARIABLE: đặt trước các biến cục bộ

§  ElementType.ANNOTATION_TYPE: đặt trước các annotation khác

§  ElementType.PACKAGE: đặt trước package

o   @Retention: thời gian sống của Annotation

§  RetentionPolicy.SOURCE: Annotation sẽ bị lược bỏ trong quá trình compile code

§  RetentionPolicy.CLASS: Annotation sẽ bị lược bỏ trong quá trình class được load

§  RetentionPolicy.RUNTIME: Annotation sẽ không bị lược bỏ mà sẽ được giữ lại trong lúc runtime.

-                   Trong annotation ConfirmPasswordNotMatch phải chứa 3 phương thức như trên vì đây là requirement của JSR-303 (Bean Validation) nếu chúng ta muốn dùng Annotation này để đánh dấu validation.

-                   Vì ràng buộc kiểm tra confirm password cần 2 field trở lên để xác định nên Annotation này cần phải là Class Annotation => ElementType.TYPE

-                   Vì chúng ta muốn ràng buộc trong lúc runtime => RetentionPolicy.RUNTIME

                                    b.               Tạo Class ConfirmPasswordValidator trong package validator

-                   Để class chúng ta là Validator thì cần phải implement interface ConstraintValidator<A, T> trong javax.validation.ConstraintValidator. Trong đó, A là annotation mà validator này được áp dụng, T là đối tượng mà Validator này sẽ áp dụng lên.

-                   Chúng ta implement 2 phương thức: initializeisValid

o   Initailize được invoke duy nhất khi class Validator được load

o   isValid trả về giá trị boolean tượng trưng cho việc có đúng ràng buộc hay không

                                    c.               Tạo Annotation interface UsernameExisted trong package validator.annotation

-                   Class UsernameExistedValidator sẽ được tạo trong phần d

Graphical user interface, text, application, email

Description automatically generated

-                   Chúng ta tạo annotation UsernameExisted tương tự như annotation ConfirmPasswordNotMatch. Tuy nhiên vì để kiểm tra liệu username có tồn tại, chúng ta chỉ cần duy nhất 1 field username nên có sự khác biệt ở nơi đặt annotation. Chúng ta sử dụng @Target là ElementType.FIELD.

                                    d.               Tạo Class UsernameExistedValidator trong package validator

Graphical user interface, text, application, email

Description automatically generated

-                   Class UsernameExistedValidator sẽ dùng cho Annotation @UsernameExisted và field username có kiểu String.

                                    e.               Thêm các annotation vào trong RegisterForm

Graphical user interface, text, application, email

Description automatically generated

-                   Ngoài các @Size đã được định nghĩa sẵn thì chúng ta sẽ sử dụng thêm 2 annotation mới được tạo là @ConfirmPasswordNotMatch và @UsernameExisted.

                                      f.               Comment out InitBinder trong class RegistrationRestController

-                    Vì chúng ta đã binding validator bằng annotation nên không cần dùng đến InitBinder để khai báo validator nữa.

                                    g.               Cập nhật ExceptionHandlerController

Graphical user interface, text, application, email

Description automatically generated

-                   @ConfirmPasswordNotMatch được áp dụng cho cả một class RegisterForm nên lỗi đó không còn là được xếp là FieldError mà trở thành GlobalError (ClassError). Vì vậy chúng ta cần duyệt thêm các GlobalError để lấy lỗi này.

-                   Khi này response object trả về sẽ bị thay đổi tên lỗi nên chúng ta tiến hành cập nhật lại register.js

                                    h.               Câp nhật register.js

Graphical user interface, text, application, email

Description automatically generated

                                   i.                  Kiểm tra kết quả

-                   Khi không nhập giá trị gì cả

-                   Nhập username đã tồn tại và confirm password không tương ứng với password đã nhập

-                   Lưu ý: Trong trường hợp nếu nhập username có độ dài không thỏa ràng buộc và đồng thời tên username đã có trong DB thì lỗi được hiển thị sẽ không nhất quán có lúc sẽ hiện “Username is existed” có lúc sẽ hiện “size must be between 6 and 20”.

-                   Điều này xảy ra bởi vì @Size@UsernameExisted trong class RegisterForm cùng áp dụng cho 1 field username. Khi Spring thực hiện kiểm tra ràng buộc sẽ thực hiện kiểm tra đồng thời cả 2 annotation nên dẫn đến việc Race Condition làm cho error message trả về không được nhất quán.

Graphical user interface, application

Description automatically generated

 

 

4.      Cấu trúc thư mục của project sau khi thực hiện hoàn tất đến các chức năng CRUD

Chúc mừng quí vị, chúng ta vừa hoàn tất việc Delete và Register Account thông qua đó chúng ta đã hoàn thành xong CRUD cho chương trình. Đồng thời chúng ta biết thực hiện 2 cách để tạo custom validator: sử dụng InitBinder và Anotation. Hy vọng nội dung này sẽ hỗ trợ quí vị trong việc sử dụng Spring Framework để xây dựng ứng dụng.

 

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 tiếp theo. Chúng ta sẽ tập trung vào việc tạo page động ở phía server và sự dụng bộ taglib của Spring cho việc Register.

 

 

 

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

Đăng nhận xét