Thứ Ba, 11 tháng 8, 2020

Bài 2: Cấu hình kết nối CSDL sử dụng DataSource và thực hiện chức năng về authentication

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

Bài 2: Cấu hình kết nối CSDL sử dụng DataSource và thực hiện chức năng về authentication

Mã Hoàng Nhật Phi

 

-         Mục đích: Sau bài viết về cấu hình tool IDE và tạo cấu trúc khung cho ứng dụng web dùng Spring 5 Framework, bài viết này định hướng để xây dựng ứng dụng CRUD. Trong bài viết này chúng ta sẽ thực hiện chức năng login để tiếp cận việc cấu hình kết nối DB. Bên cạnh đó, chúng ta sẽ thực hiện sử dụng khả năng dependencies trong Spring Framework.

-         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. Tham khảo tại địa chỉ 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

o        Đã hoàn tất thiết lập ide cho việc lập trình sử dụng Spring và tạo cấu trúc khung cho ứng dụng MVC2 Design Pattern sử dụng Spring MVC5 tại địa chỉ http://www.kieutrongkhanh.net/2020/06/bai-1-cau-hinh-netbeans-va-web-project.html

-         Tool sử dụng:

o   Netbeans 8.x trở lên

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 số 1 về khởi tạo, cấu hình để có một ứng dụng sử dụng Spring Framework 5 và build tool Maven, chúng ta sẽ chuyển sang xây dựng ứng dụng web với các chức năng login, cụ thể ở đây là bước tiếp cận đầu tiên để kết nối và truy vấn được dữ liệu từ DB thông qua Data Source. Trong quá trình thực hiện, chúng ta sẽ tiếp cận các khái niệm dependencies để từ đó sử dụng chúng trong Spring Framework.

                                        II.                        Nội dung

-         Một ứng dụng Spring web hoạt động theo mô hình sau với front controller là DispatcherServlet:

-         Chúng ta sẽ ứng dụng mô hình nêu trên để xây dựng ứng dụng trong phần tiếp theo.

1.                  Chức năng login

-         Chúng ta bắt đầu thực hiện chức năng xác thực người dùng vào trong ứng dụng bằng cách chỉnh sửa project đã cấu hình trong bài 1 để có trang login như sau:

o   Chúng ta tạo trang login.jsp trong project tại thư mục view (thư mục này trong thư mục WEB-INF) như sau

o   Chúng ta tiếp tục điều chỉnh MainController như sau

§  Ở đây, chúng ta cần comment lại toàn bộ code của hàm getIndex như hình bên dưới

§  Bổ sung hàm getLoginPage như code bên dưới

§  Ở đây, chúng ta không có sử dụng model cho nên chúng ta sẽ dùng constructor một tham số của ModeAndView. Đối tượng này chỉ nhận vào một string là tên view name. Tên được truyền vào là giá trị của action trong form.

 

-         Chúng ta tạo tiếp table trong database (quí vị có thể đặt tên Database tùy ý) có cấu trúc như sau:

-         Chúng ta thêm thông tin cấu hình việc giao tiếp với database vào file application-config.xml như sau

o   Thực hiện mapping và khai báo data source để kết nối db động cơ bản trong ứng dụng web (tham khảo tại địa chỉ http://www.kieutrongkhanh.net/2016/08/tao-ket-noi-ong-en-db-trong-mo-hinh-mvc.html)

o   Thực hiện khai báo entity để mapping dữ liệu sử dụng JPA (Tham khảo thêm tại địa chỉ https://docs.oracle.com/javaee/6/tutorial/doc/bnbpz.html và tại địa chỉ http://www.kieutrongkhanh.net/2016/08/ung-dung-jpa-vao-mvc2-ket-hop-javaee6.html)

-                      Các nội dung cấu hình mà chúng ta vừa tạo ra là 03 bean: dataSource, entityManagerFactory, transactionManager.

o   Bean transactionManager sẽ là bean của JPA để quản lý transaction. Bean này chứa một bean để quản lý các entity được cung cấp bởi provider như Hibernate, EclipseInk là entityManagerFactory.

o   Bean entityManagerFactory tham chiếu tới một bean là dataSource để trỏ tới database mà ứng dụng sẽ tương tác.

o   Tag tx:annotation-driven để bật cấu hình của transactional bằng annotation.

o   Tag jpa:repositories dùng để chỉ cho spring biết package để thực hiện quét các repository.

o   Tag context:component-scan dòng 49 được dùng để cho spring biết các file trong package trên là tên package mà Spring framework sẽ lấy để quét các package để tìm các class để tạo ra Spring bean cho ứng dụng Spring. 

§  Trong khái niệm lookup object, chúng ta cần lưu ý những điều như sau

·        Nếu kiểu B (class) là một thuộc tính của kiểu A (class) theo dạng

class A {

            private B b;

}

o   Ở đây, concept nói rằng B là dependencies của A thì việc cấu hình lookup là A để đưa B vào

o   Ở bài này, chúng ta nhận thấy Service được đặt trong quá trình scan để được Spring lookup (không phải là Repository). Qua điều này, chúng ta nhận thấy cấu trúc mà đang sử dụng trong project ở các bước trên tương tự như concept nêu trên → repository là dependencies của services

o   Đa phần chúng ta không hiểu rõ và cấu hình sai nội dung lookup. Nguyên nhân cơ bản ở đây là chúng ta luôn nghĩ B là dependencies thì B phải được Spring lookup để đưa cho A → lỗi cơ bản phát sinh như sau

….

tên class lookup sai (ở đây nếu chúng ta lookup repository (B) thì nó sẽ báo lỗi là class services (A))

o   Ở đây, chúng ta cấu hình cho hibernate với các giá trị show_sql; hbm2ddl.auto để tự update schema khi có thay đổi trong code; dialect để lấy physical naming của schema dưới database trong quá trình ánh xạ dữ liệu trong xử lý.

·        show_sql: chúng ta bật chế độ này trong quá trình phát triển ứng dụng, mục tiêu để biết câu sql được ORM generate ra nhầm đánh giá và tối ưu việc thực thi của ứng dụng. show_sql sẽ được tắt khi triển khai ứng dụng chạy thực tế.

·        hbm2ddl.auto có 5 chế độ:

o   validate: Kiểm tra xem schema giữa java code và database có đúng với nhau hay không trong quá trình deploy (không làm thay đổi database).

o   update: tự động update schema khi có thay đổi trong java code.

o   create: Xóa toàn bộ schema trước đó và tạo mới.

o   create-drop: tạo mới và xóa toàn bộ schema khi SessionFactory bị đóng (trường hợp thường xảy ra là ứng dụng dừng).

o   none: không làm gì tại bước triển khai ứng dụng.

·        Spring cung cấp cho chúng ta một bộ dialect class. Các class này làm nhiệm vụ giao tiếp giữa spring data jpa và RDBMS.

o   Chúng ta cần chọn lựa dialect phù hợp với RDBMS mà chúng ta phát triển để tránh các lỗi về lệch phiên bản từ đó tạo ra các lỗi khó đoán biết được.

o   Trong một số trường hợp cụ thể, chúng ta cũng có thể custom lại dialect dựa trên dialect của framework cho phù hợp với nhu cầu sử dụng.

o   Hibernate có thể mapping giữa java code và database dựa trên một convension nhất định. Để tránh nhầm lẫn, chúng ta chỉ định lấy tên vật lý trong database để dễ dàng cho việc ánh xạ giữa java code và database.

Chúng ta có thể tham khảo các bài viết tiếp theo (chủ đề đổi datasource cho ứng dụng) để thấy được sự linh động khi phát triển ứng dụng bằng JPA và các provider ORM ở đây cụ thể là Hibernate.

-                      Chúng ta tạo ra class Registration thuộc về package domain trong package của chúng ta hiện có (ví dụ ở đây là com.phimhn.springmvc) là model mapping 1-1 với một dòng trong bảng TblRegistration ở database (cấu trúc JavaBeans, DTO hay POJO) như sau:

-                      Chúng ta bổ sung annotation

o   @Entity để đánh dấu cho EntityManagerFactory quản lý các instance của class này là ánh xạ từ database.

o   @Table để chỉ định bảng mà class này sẽ ánh xạ xuống database.

-                      Chúng ta thực hiện tạo repository interface cho entity vừa tạo vào package repository theo cấu hình mapping trong application-config.xml mà chúng ta vừa cấu hình ở các bước trước

-   Trong một repository của Spring, chúng ta chỉ cần khai báo interface còn việc hiện thực hóa Spring data JPA sẽ do hệ thống hỗ trợ phát sinh giúp chúng ta.

-   JpaRepository nhận 2 generic type lần lượt là entity, kiểu dữ liệu của khóa chính.

o   Khi tạo ra interface, chúng ta extends để chọn class JPA support thì chúng ta có thấy lỗi như sau và tool không thể suggest cho chúng ta như hình bên dưới

o   Lỗi này là do chưa có thư viện support hay nói cách khác là project chưa được add dependencies. Chúng ta thực hiện các bước sau để add dependencies bằng tool

§  Chúng ta mở cửa sổ project trong netbeans, mở rộng mục Dependencies

§   Click phải chuột trên Dependencies, chọn

§  Cửa sổ Dependencies xuất hiện, chúng ta điền các giá trị gói thư viện jpa cần download như là group id, artifact ID và Version. Click nút Add

§  Quá trình download được thực hiện và được cập nhật trên tool lẫn file cấu hình pom.xml

§  Chúng ta có thể không cần dùng tool như các bước đã nêu. Chúng ta thực hiện mở file pom để điền nội dung dependency tương tự như nội dung được phát sinh ở hình trên.

§  Chúng ta thực hiện chạy lệnh maven clean install để download gói thư viện về. Kết quả trên tool của dependencies được cập nhật (mất đi dấu chấm thang trong bảng màu vàng)

§  Chúng ta thực hiện gõ code, Netbeans sẽ visual và suggest cho chúng ta để lựa chọn extend JpaRepository và import thư viện giúp chúng ta.

-                      Repository của Spring có quy tắc đặt tên hàm để generate ra câu query tương ứng. Netbeans hiện tại chưa hỗ trợ suggest cho Spring data JPA, một số IDE khác như IntelliJ đã hỗ trợ suggest.

-                      Chúng ta xây dựng các phương thức để truy vấn dữ liệu như sau:

o   Phương thức countByUsernameAndPassword là hàm đến xem có bao nhiêu record ở database có username và password tương ứng.

o   Phương thức findByUsername là hàm tìm ra record có username tương ứng ở bảng TblRegistration trong database.

o   Spring data JPA cung cấp sẵn một phương thức là findById có tham số là kiểu dữ kiệu của khóa chính, ở Spring 4 phương thức trên trả về chính xác entity ta khai báo nhưng từ Spring 5 kết quả trả về đươc wrap lại bởi kiểu dữ liệu Optional của Java 8. Ở đây, chúng ta khai báo lại một phương thức findByUsername để lấy thẳng entity để thuận tiện cho quá trình hiện thực hóa.

-          Chúng ta tiếp tục tạo service có tên là RegistrationService đặt tại source package trong package service xử lý tính năng đăng nhập như sau:

-                      Ở RegistrationService, ta có hai annotation là:

o   @Service: Tạo ra một Spring bean do BeanFactory quản lý. Sự khác nhau giữa @Controller hoặc @RestController, @Service, @Repository nằm ở thứ tự khởi tạo của các spring bean, như repository phải được khởi tạo trước service và tương tự service phải khởi tạo trước controller hoặc REST controller.

-                      Chúng ta cập nhật lại code trong controller với method login và method logout.

o   Một method của controller có thể đón nhận tham số là HttpServletRequest (Request Object) để hỗ trợ chúng ta thao tác các đối tượng trong container tương tự như Servlet Model. Áp dụng điều này với chức năng login để lấy các parameter cần thiết để check login

o   Bên cạnh đó, một method trong controller cũng có thể đón nhận tham số là HttpSession. Chúng ta sẽ áp dụng điều này với chức năng logout nhằm hủy session và trả về trang Login

-                      Trong code phần method login ở hình bên trên, chúng ta nhận thấy có một giá trị Constants.ATTR_USER là một hằng số do chúng ta định nghĩa để có thể sử dụng ở nhiều nơi trong source code nên ta đặt tại interface để chứa biến dùng chung cho cả ứng dụng như sau

-                      Ở trong class RegistrationService, chúng ta nhận thấy có một final field là registrationRepository và constructor truyền giá trị để đón nhận field này. Bên cạnh đó, class MainController cũng có một final field là registrationService truyền vào constructor. Tuy nhiên, trong quá trình implement trong các bước nêu trên, chúng ta đã không viết code tạo ra instance cho hai class trên. Câu hỏi đặt ra là làm sao hệ thống có thể vận hành được khi các đối tượng chưa được khởi tạo?

o   Vấn đề được giới thiệu ở đâu là Spring sẽ cung cấp Spring IoC container.

§  Spring IoC container làm nhiệm vụ quản lý các dependency (sự phụ thuộc giữa các class với nhau ở mức khái niệm chứ không liên quan đến file pom.xml). Thông thường để truy cập một xử lý, chúng ta thường tạo ra đối tượng (dùng toán tử new) và thực hiện gọi method cần thiết. Điều này làm các class ở tầng cao hơn sẽ phụ thuộc vào các class ở tầng thấp hơn (thành phần Business Logic Object – BLO sẽ phụ thuộc vào Data Access Object – DAO). Do đó, khi có sự thay đổi để đối tượng phụ thuộc ở tầng thấp thì sẽ ảnh hưởng đến đối tượng ở phần trên → Điều này tạo ra tight coupling.

            Chúng ta xem xét ví dụ như sau:

→ Ở đây, chúng ta có 02 class là Service và Repository. Service đang gọi method xử lý của Repository từ đó chúng ta nhận thấy Service đang phụ thuộc vào Repository.

Như vậy nếu như ta muốn thay đổi cách xử lý từ Repository sang class mới là NewRepository thì code trong Service sẽ bị đổi.

            Chúng ta cùng nhận xét ví dụ tiếp theo

·        Chúng ta thay đổi code của Service như sau

·        Chúng ta tiếp tục tạo ra một interface là Repository

·        Chúng ta thực hiện implement 02 class implementation của interface đã được định nghĩa ở trên.

 

·        Nếu ta muốn thay đổi xử lý Repository mới chỉ cần thực hiện chỉnh sửa như sau:

 

·        Như vậy, qua ví dụ nêu trên chúng ta đã có 02 cách thức xử lý khác nhau mà không cần phải sửa code của Service lại.

→ IoC container cũng làm công việc tương tự thao tác tạo ra các object để thực hiện các xử lý như ví dụ trên trong hàm main và đưa các object xử lý đó vào các nơi yêu cầu object đó.

→ Nói cách khác IoC container như một quản lý điều phối các object cần xử lý được Spring Framework tạo ra. IoC container nhầm mục đích thỏa mãn nguyên tắc thiết kế Dependency Inversion trong SOLID và loose coupling giữa các object tương tác với nhau.

§  IoC container sẽ phát sinh các instance cần thiết, phù hợp vào những nơi mà các object yêu cầu. Đây là thể hiện của Inversion of Control của Spring framework mà cụ thể là qua design pattern Dependency Injection.

·        Dependency Injection là design pattern cho phép các module phụ thuộc (dependency) sẽ được tiêm (inject) vào các module cấp cao hơn.

·        Như ví dụ trên, repository là module phụ thuộc sẽ được tiêm vào trong module cấp cao hơn là service thông qua constructor. Điều này đảm bảo các module cấp cao không phụ thuộc vào các module cấp thấp thỏa mản Dependency Inversion.

·        Bên cạnh đó Inversion of Control phải tuân thủ theo Dependency Inversion nên các module phụ thuộc (dependency) và các module cấp cao hơn phải giao tiếp với nhau thông qua giao diện (interface).

§  Spring IoC container dùng reflection để hiện thực hóa.

·        Reflection là một tập API do Java cung cấp để giao tiếp (lấy thông tin, thay đổi behavior) của các method, class, interface tại thời điểm runtime.

§  Spring sử dụng 03 cách để phát sinh dependency vào:

o   Constructor-based

o   Field-based

o   Setter-based

o   Trong nội dụng bài viết này, chúng ta sẽ dùng constructor-based để đưa dependency vào các class. Thông qua việc sử dụng constructor để phun dependency, chúng ta đã hiện thực hóa luôn immutable object design pattern.

§  Immutable object là một design pattern nhằm đảm bảo object không bị thay đổi trạng thái (không có các phương thức để thay đổi giá trị trong object đó).

-                      Chúng ta thực hiện tạo trang invalid.jsp vào thư mục view như hình bên dưới

-                      Chúng ta tiếp tục tạo trang search.jsp vào thư mục view như hình bên dưới

-                      Chúng ta thực hiện chạy lệnh clean install để build ứng dụng

-                      Chúng ta thực hiện việc deploy ứng dụng để test thử

o   Quá trình deploy chúng ta sẽ phát sinh lỗi như sau

o   Lỗi này phát sinh là do chúng ta chưa add các dependencies về JPA - Hibernate cho project đang làm cùng với việc không nhận thấy rõ lỗi đang xảy ra

o   Chúng ta mở file pom.xml để thực hiện bổ sung các dependencies cho Hibernate như sau

o   Các dependencies cho việc ghi log file

§  Nếu thiếu các dependencies này thì chúng ta sẽ thấy lỗi như sau ở phía server

o   Các dependencies về driver để truy cập database

o   Chúng ta thực hiện lệnh clean-install để cập nhật cho project

o   Các dependencies trong project chúng ta hiện có như sau

o   Chúng ta thực hiện deploy ứng dụng. Quá trình deploy sẽ chạy rất nhiều log cho chúng ta thấy từ việc tạo datasource, kết nối DB, entity  …. Cho đến khi nào thấy finised mới thành công – nếu khác thì có deploy được cũng không chạy được

o   Log của chúng ta sẽ nhìn thấy như sau

o   Chúng ta thực hiện ứng dụng

-                      Chức năng login trên để minh họa cho quá trình xử lý chứ không liên quan đến spring security. Chúng ta sẽ tiếp cận spring security trong các bài viết sau.

Chúng ta vừa hoàn tất việc cấu hình kết nối DB để xây dựng chức năng Login áp dụng Spring 5 Framework. Rất mong nội dung này sẽ hỗ trợ quí vị bước đầu trong tiếp cận Spring Framework trong việc tiếp cậ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 CRUD kết hợp với Spring 5 Framework.

 

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

Đăng nhận xét