Xây dựng ứng dụng CRUD với Spring 5
Bài 7: Xây dựng ứng dụng shopping cart
Trương Trần Tiến
- Mục đích: Ở bài viết trước chúng ta đã thực hiện thành công chức năng CRUD đơn giản sử dụng Framework Spring 5. Bài viết này sẽ hướng dẫn chúng ta cách hiện thực ứng dụng shopping cart thông qua sử dụng XMLHTTPRequest để gọi RESTful API. Sử dụng Javascript để trình bày dữ liệu ở phía người dùng.
- 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 Nắm vững flow của một ứng dụng Shopping Cart sử dụng MVC. http://www.kieutrongkhanh.net/2016/08/ung-dung-shopping-cart-su-dung-mvc.html
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 và 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
I. Giới thiệu
- Ở bài viết trước chúng ta đã thực hiện thành công chức năng CRUD đơn giản sử dụng Framework Spring 5. Bài viết này sẽ hướng dẫn chúng ta cách hiện thực ứng dụng shopping cart thông qua sử dụng XMLHTTPRequest để gọi RESTful API. Sử dụng Javascript để trình bày dữ liệu ở phía người dùng. Bài viết được chia thành 2 phần: tạo một trang book shop và hiện thực chức năng của shopping cart
II. Nội dung
1. Tạo trang book shop
a. Tạo bảng mới trong database
- Chúng ta thực hiện tạo bảng TblProduct trong database (có thể đặt tên bảng, database tùy ý).
- Ở cột primary key “id”, chúng ta sẽ cấu hình identity – key tự tăng
- Cấu hình Datasouce cho database đã được giới thiệu ở bài 2 (http://www.kieutrongkhanh.net/2020/08/bai-2-cau-hinh-ket-noi-csdl-su-dung.html)
b. Tạo domain class Product
- Chúng ta tạo class Product thuộc về package domain với prefix như những bài trước (ví dụ: com.tientt.springmvc.domain).
- Class Product là model mapping 1-1 với bảng TblProduct ở database và có cấu trúc như một JavaBean
- Chúng ta sử dụng @GeneratedValue cùng với stategy IDENTITY để đánh dấu đây là key tự tăng
c. Tạo class ProductDTO
- Chúng ta tiếp tục tạo class ProductDTO - một JavaBean - để lưu trữ dữ liệu ở tầng Service
- Chúng ta cần override lại method hashCode() để so sánh 2 object dựa vào id khi lưu trữ trong Map. Điều này sẽ giúp cho chúng ta trong việc sử dụng ProductDTO làm key trong Map – sẽ được áp dụng ở phần sau Add to cart.
d. Tạo class ProductResponseModel
- Tiếp theo, chúng ta tạo tiếp class ProductResponseModel ở package responsemodel. Đây sẽ là class trực tiếp dữ liệu về phía client khi sử dụng RESTful API hay là AttributeModel.
- Tới đây, chúng ta đã hoàn tất tạo 3 class chứa dữ liệu tương ứng cho 3 lớp:
o Class Product cho lớp Reposistory
o Class ProductDTO cho lớp Service
o Class ProductResponseModel cho lớp Controller
e. Tạo mapper tương ứng cho ProductDTO và ProductResponseModel
- Chúng ta tiến hành tạo các Interface để mapping giữa 3 class dữ liệu được đề cập ở trên.
o Interface ProductDTOMapper sẽ mapping Product sang ProductDTO
o Interface ProductResponseMapper sẽ mapping ProductDTO sang ProductResponseModel
- Class implementation của các interface sẽ được mapstruct tạo ra trong quá trình build. (http://www.kieutrongkhanh.net/2020/08/bai-3-thuc-hien-viec-truy-van-va-trinh.html)
f. Tạo ProductRepository
- Chúng ta tạo Interface ProductReposistory trong package repository để có thể query được dữ liệu từ database.
g. Tạo class ProductService trong package service
- Chúng ta tạo class ProductService để xử lý các business logic liên quan đến Product như CRUD, findAll, …..
- Hiện tại chúng ta inject 2 bean ProductRepository và ProductDTOMapper bằng Setter
- Phương pháp này được khuyến khích sử dụng hơn so với inject trên field hoặc inject thông qua constructor bởi vì chúng ta sẽ dễ dàng quản lý các bean được inject. Ngoài ra trong lúc mock testing, chúng ta có thể dễ dàng linh hoạt inject các bean mà không cần Spring Context.
h. Tạo ProductRestController trong package controller
- Chúng ta tạo một class controller mới tên ProductRestController và inject 2 bean ProductService và ProductResponseModelMapper bằng setter.
- Class này là một RestController được đường dẫn bắt đầu bằng “/api/v1/products”
o “api” để chỉ rằng URL này là một RESTfull API endpoint
o “v1” để đánh dấu version của API
- Chúng ta sử dụng @RequestMapping ở trước class để chỉ rằng đây là đường dẫn root cho tất các các đường dẫn mapping ở trong.
o Ví dụ: trong bài viết method getAllProduct được mapping là “” có nghĩa rằng để truy cập được method này client cần truy cập “localhost:8080/spring-mvc/api/v1/products”
- Method getAllProduct sẽ trả cho người dùng một chuỗi JSON chứa các thông tin của các sản phẩm. Chuỗi JSON có cấu trúc như sau:
i. Tạo shop.jsp, cập nhật login.jsp, cập nhật MainController
- Chúng ta tạo trang shop.jsp trong /WEB-INF/view để hiển thị danh sách các sản phẩm
- Chúng ta sẽ đính kèm file shop.js đồng thời sử dụng attribute “onload” ở tag <body>, khi nào trang được load thì hàm getProducts() trong shop.js sẽ được thực thi
- Link “showCart” sẽ được mapping ở phần sau
- Chúng ta tiến hành thêm link dẫn đến shop.jsp trong trang login.jsp
- Chúng ta tiến hành tạo mapping để map shop.jsp trong class MainController
j. Tạo shop.js
- Chúng ta tạo file shop.js trong folder resources/js để có thể request API và nhận kết quả JSON và render dữ liệu cho người dùng
- Hiện tại hàm addToCart được để trống và sẽ được hiện thực ở phần sau.
k. Chạy maven goal với clean and install, deploy và chạy test kiểm tra kết quả
Tại màn hình login nhấn link “Click here to buy books”
Notes:
- Trong quá trình demo trên tomcat chạy rời hay tomcat chạy với Netbeans sẽ có khả năng thấy những lỗi như sau
…
…
…
…
…
…
- Đây là bug của một số Tomcat version. Chúng ta bỏ qua vì nó không ảnh hưởng đến ứng dụng. Ngoài ra, muốn loại bỏ nó thì chúng ta cần phải thực hiện cấu hình trong file Catalina.properties của tomcat để skip các gói jar khi nó scan. Tình trạng này sẽ xảy ra tùy theo từng tomcat khác nhau
2. Thực hiện các chức năng của shopping cart
a. Tạo class CartProductResponseModel
- Chúng ta thực hiện tạo class CartProductResponseModel trong package responsemodel để chứa những thông tin sản phẩm trong cart. “id”, “name”, “price” là thông tin của sản phẩm, “quantity” là số lượng sản phẩm người dùng add vào cart.
b. Tạo interface mapper CartProductResponseModelMapper
- Chúng ta tạo một Interface mapper mới để mapping từ ProductDTO sang
- Lưu ý: vì class ProductDTO không có field quantity nên mapstruct sẽ không thực hiện mapping do đó field quantity ở CartProductResponseModel sẽ có giá trị mặc định là 0.
c. Tạo class CartService
- Chúng ta tiến hành tạo class CartService ở package service. Đây là class tượng trưng cho một shopping cart, sẽ xử lý các tác vụ liên quan đến việc thêm vào giỏ, xóa sản phẩm ra khỏi giỏ.
- Annotation @SessionScope sẽ đánh dấu cho Spring Container biết được life cycle của bean này gắn liền với life cycle của HttpSession. Điều này có nghĩa rằng cứ mỗi session thì container sẽ tạo ra một bean CartService gắn liền với session đó.
- Spring cung cấp cho chúng ta 6 scope cho bean:
o Singleton: bean sẽ chỉ được khởi tạo 1 lần duy nhất, bất kì inject nào cũng sẽ lấy từ reference của bean ban đầu. Đây là scope default.
o Prototype: bean sẽ được khởi tạo mới khi có yêu cầu inject
o Request: life cycle của bean gắn liền với life cycle của RequestObject
o Session: life cycle của bean gắn liền với life cycle của Session
o Global-session: life cycle của bean gắn liền với life cycle của Portlet session
o Application: life cycle của bean gắn liền với life cycle của Servlet Context
- Class CartService có một property là products có kiểu là Map<ProductDTO, Integer> chứa thông tin về mặt hàng trong giỏ và số lượng mặt hàng đó.
- Đồng thời, chúng ta tiến hành inject các bean ProductRepository, ProductDTOMapper, CartProductResponseModelMapper bằng setter.
- Tiếp theo, chúng ta hiện thực 2 hàm để lấy toàn bộ thông tin sản phẩm hiện có trong giỏ hàng.
- Hàm getProductAsList sẽ duyệt keySet của map products, mapping ProductDTO thành CartProductResponseModel và gán số lượng sản phẩm trong giỏ hàng vào field quantiy. Cuối cùng sẽ trả về một List các CartProductResponseModel để convert thành chuỗi JSON trả về cho Client
- Tiếp theo, chúng ta hiện thực chức năng add vào giỏ hàm. Chúng ta sẽ cung cấp 2 method nhận trực tiếp ProductDTO và nhận và ID sản phẩm.
- Đối với hàm nhận vào ID của sản phẩm, chúng ta gọi Reposistory nếu như không tìm thấy Product sẽ quăng lỗi EntityNotFoundException
- Tương tự như chức năng add to cart, chúng ta cũng hiện thực 2 hàm removeProduct với 2 tham số là ProductDTO và productID
d. Tạo class CartRestController
- Chúng ta tiến hành tạo CartRestController trong package controller để cung cấp các API. Tương tự như ProductRestController, chúng ta cần đặt một root path để có thể truy cập được các các mapping liên quan đến Cart.
- Chúng ta inject bean CartService thông qua setter
- Chúng ta tạo một Mapping với GET method sẽ gửi về phía client các sản phẩm đang có trong giỏ hàng.
- Lưu ý: vì chúng ta cần gửi một chuỗi JSON nên chúng ta không được gửi RepsonseEntity có body là null mà phải là một List rỗng.
- Chúng ta thêm một Mapping với POST method để xử lý việc add giỏ hàng.
- Trong giá trị của mapping URL, chúng ta dùng “{productID}” và trong parameter của hàm chúng ta dùng annotation @PathVarible. Spring sẽ tự động binding bất kỳ giá trị nào nằm ở {productID} sẽ được convert sang Interger productID.
o Truy cập ở /products/1 => Integer productID = 1
o Truy cập ở /products/23 => Interger productID = 23
o Truy cập ở /products/abc => quăng lỗi TypeMismatchException
- Nếu add thành công chúng ta trả về cho người dùng Success Message
- Tương tự như trên, chúng ta hiện thực mapping DELETE remove sản phẩm khỏi giỏ hàng, cũng sử dụng @PathVarible để binding giá trị trên URL thành parameter của hàm mapping.
- Khi remove thành công, ta trả về cho phía người dùng Success Message
e. Cập nhật class ExceptionHandlerController
- Chúng ta tiến hành cập nhật ExceptionHandlerController để có thể xử lý lỗi TypeMismatchException được đề cập ở trên
- Lưu ý: TypeMismatchException được import từ org.springframework.beans.TypeMismatchException
f. Tạo cart.jsp, tạo mapping trong MainController
- Chúng ta tạo cart.jsp trong /WEB-INF/view để hiện danh sách giỏ hàng của người dùng.
- Hàm getCart() sẽ được thực thi mỗi khi trang được load.
- Tiếp đến, chúng ta tạo mapping trang cart.jsp trong MainController.
g. Tạo cart.js
- Chúng ta tiến hành tạo file cart.js để có thể request đến API và hiển thị dữ liệu
- Khi remove product thành công, chúng ta sẽ gọi lại hàm getCart() lại để render lại dữ liệu trong cart
h. Cập nhật hàm addToCart trong shop.js
- Ở phần 1, ở giao diện shop chúng ta có button “add to cart” nhưng vẫn để trống hàm addToCart(id). Chúng ta hiện thực chức năng đó ở phần này.
- Khi add thành công, tạo màn hình alert thông báo
i. Chạy maven goal để thực hiện clean and build, delop, thực hiện và kiểm tra kết quả
- Tại thời điểm deploy ứng dụng có khả năng phát sinh các lỗi như sau
- Lỗi này phát sinh là do annotation @SessionScope trong CartService gây nên trong quá trình inject và khởi tạo đối tượng. Nguyên nhân của lỗi này là do lý do gì và chúng ta cần phải khắc phục như thế nào?
o Lỗi này xảy ra khi bị xung đột thư viện trong quá trình cấu hình để sử dụng dependency trong ứng dụng
o Trong tập tin pom.xml, dependency org.springframework.data cần spring framework 5.1.0
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
o Nếu chúng ta thiết lập để ở dependency này ở ngay đầu, rồi mới thêm mấy dependecy của spring framework version 5.2.0. Điều này sẽ gây nên xung đột ngay khi ứng dụng phát sinh code và reference bởi vì khi đó maven đã cài đặt spring framework 5.1.0 rồi
o Giải pháp của vấn đề này, chúng ta nên đưa dependecy này xuống dưới cùng của tag dependecies trong file pom.xml với mục tiêu để maven cài spring framework 5.2.0 trước, rồi khi đó gặp 5.1.0 nó sẽ ommited.
o Nếu quí vị có sử dụng tool Inteliji, chúng ta sẽ nhận thấy dễ dàng vấn đề đang xảy ra
o Chúng ta thực hiện clean and install, deploy và test lại. Lỗi này sẽ không còn tồn tại nữa
- Lỗi thứ 2 có khả năng xuất hiện là lỗi liên quan đến việc bắt lỗi TypeMisMatch khi thực hiện deploy
o Lỗi này xảy ra khi lớp ExceptionHandlerController extends ResponseEntityExceptionHandler, trong lớp ResponseEntityExceptionHandler đã có sẵn method handleTypeMismatch, nếu khi nếu chúng ta tự tạo hàm handleTypeMismatch (overloading) thì Spring sẽ không biết được sẽ invoke hàm nào khi gặp exception.
o Giải pháp để giải quyết vấn đề này, chúng ta thực hiện điều chỉnh hàm handleTypeMismatch cho đúng signature như sau
o Thực hiện clean and install với maven goal, deploy và test lại sẽ hết lỗi khi deploy
- Chúng ta thực hiện chạy kiểm thử chương trình bắt đầu với việc chuyển đến trang mua sách
Trang được chuyển tới và load các sản phẩm từ DB lên và show trên lưới dữ liệu. Chúng ta thực hiện click Add To cart trên các sản phẩm chúng ta muốn mua
Click Link Click here to show your cart để thấy được các sản phẩm đã lựa chọn
Chúng ta thực hiện nhấn nút Remove để loại sản phẩm lựa chọn ra khỏi giỏ
- Tại đây chúng ta có thể gặp phải trường hợp là nhấn Remove nhưng giao diện không refresh và tắt mở trình duyệt lại thì browser được cập nhật. Đây là vấn đề gặp phải khi chúng ta sử dụng trình duyện IE
o Lỗi xảy ra tại vì IE 11 cache lại trang web (được đề cập ở bài appendix bài 4, www.kieutrongkhanh.net/2020/11/bai-4-thuc-hien-viec-update-du-lieu-va.html)
o Sửa bằng cách thêm stamptime ở XHR ở hai method removeProductInCart và getCart trong cart.js
o Chúng ta có thể clean and install, deploy và kiểm tra lại. Kết quả sẽ cập nhật đúng và giao diện sẽ refresh
Chúc mừng các bạn đã hoàn tất được ứng dụng Shopping Cart sử dụng Spring 5 Framwork. Chúng tôi hy vọng nội dung của bài này giúp ích các bạn trong việc sử dụng Spring để xây dựng một web application.
Rất mong sự góp ý chân thành và chia sẻ của quí vị về vấn đề này. Hẹn gặp quý vị ở phần tiếp theo – chúng ta sẽ thực hiện việc thay đổi cơ sơ dữ liệu và custom mapping response message trả về sử dụng. Bên cạnh đó, chúng ta sẽ tìm hiểu về các nội dung liên quan đến Spring Security trong loạt bài về Spring MVC 5.
Không có nhận xét nào:
Đăng nhận xét