Mình đã hướng dẫn các bạn cách sử dụng JPA trong Spring framework như thế nào trong bài viết trước. Trong bài viết này mình sẽ tiếp tục hướng dẫn các bạn cách sử trong JPA trong Spring MVC các bạn nhé!
Đầu tiên, mình cũng sẽ tạo một Maven project để làm ví dụ:
Bạn nào chưa biết cách tạo thì làm theo hướng dẫn của bài viết này nhé các bạn!
Các dependencies mặc định mình đã thay đổi version như sau:
1 2 3 4 5 6 |
<properties> <java-version>1.8</java-version> <org.springframework-version>5.0.4.RELEASE</org.springframework-version> <org.aspectj-version>1.8.13</org.aspectj-version> <org.slf4j-version>1.7.25</org.slf4j-version> </properties> |
Để làm việc với JPA, chúng ta cần thêm spring-orm dependency nữa:
1 2 3 4 5 |
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${org.springframework-version}</version> </dependency> |
Để làm việc với JPA với implementation là Hibernate, mình cần thêm dependency của Hibernate như sau:
1 2 3 4 5 |
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.2.14.Final</version> </dependency> |
MySQL Connector:
1 2 3 4 5 |
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.6</version> </dependency> |
Mình sẽ sử dụng Project Lombok để khỏi phải khai báo những phương thức Getter hay Setter, …
1 2 3 4 5 6 |
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.20</version> <scope>provided</scope> </dependency> |
Để chạy project này, mình sẽ sử dụng Maven Jetty Pluggin:
1 2 3 4 5 |
<plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>9.4.8.v20171121</version> </plugin> |
Trong ví dụ này, mình sẽ tạo một ứng dụng web nhỏ cho phép người dùng có thể truy vấn thông tin của một sinh viên.
Chúng ta sẽ tổ chức project theo mô hình: từ Controller muốn thao tác qua database phải thông qua các class Service. Các class Service sẽ đóng vai trò trung gian và những thứ liên quan đến database sẽ không được sử dụng trực tiếp trong Controller.
Để làm được điều này, chúng ta cần thực hiện lần lượt từng bước sau:
Đầu tiên, chúng ta cần định nghĩa database lưu thông tin của sinh viên.
Để đơn giản, mình sẽ định nghĩa một table chứa thông tin sinh viên với 2 cột như sau:
1 2 3 4 5 |
CREATE TABLE `student` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1; |
Tiếp theo, mình sẽ định nghĩa entity Student thể hiện thông tin của table student.
Trong entity này, mình sẽ sử dụng annotation @Data của Project Lombok để khỏi phải khai báo các phương thức Getter, Setter, toString(), equals() hay hashCode().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.huongdanjava.springmvcjpa.entity; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import lombok.Data; @Table(name = "student") @Entity @Data public class Student implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; @Column private String name; } |
Để làm việc với JPA, tương tự như trong Spring framework, chúng ta cũng cần một tập tin persistence.xml cấu hình cho nó.
Các bạn hãy tạo mới tập tin persistence.xml nằm trong thư mục /src/main/resources/META-INF các bạn nhé!
Nội dung của tập tin này như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="springPU" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <class>com.huongdanjava.springmvcjpa.entity.Student</class> <exclude-unlisted-classes /> <properties> <property name="hibernate.show_sql" value="true" /> <property name="hibernate.format_sql" value="false" /> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" /> <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" /> <property name="javax.persistence.validation.mode" value="none" /> </properties> </persistence-unit> </persistence> |
Bây giờ, chúng ta cũng đi khai báo EntityManagerFactory trong Spring container.
Tương tự như trong Spring framework, mình cũng sử dụng LocalContainerEntityManagerFactory và khai báo nó như sau trong tập tin /src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<beans:bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <beans:property name="dataSource" ref="dataSource" /> <beans:property name="persistenceUnitName" value="springPU" /> <beans:property name="jpaVendorAdapter"> <beans:bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> </beans:property> </beans:bean> <beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <beans:property name="driverClassName" value="com.mysql.cj.jdbc.Driver" /> <beans:property name="url" value="jdbc:mysql://localhost:3306/jpaexample" /> <beans:property name="username" value="root" /> <beans:property name="password" value="123456" /> </beans:bean> |
Chúng ta sẽ sử dụng interface StudentDao và implementation của nó là StudentDaoImpl để thao tác trực tiếp với database.
Interface StudentDao có nội dung như sau:
1 2 3 4 5 6 7 8 |
package com.huongdanjava.springmvcjpa.dao; import com.huongdanjava.springmvcjpa.entity.Student; public interface StudentDao { Student findById(Long id); } |
Class StudentDaoImpl implement interface StudentDao sẽ được khai báo với annotation @Repository (dùng để khai báo với Spring bean này sẽ làm việc với database). Nội dung của class này như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.huongdanjava.springmvcjpa.dao.impl; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.springframework.stereotype.Repository; import com.huongdanjava.springmvcjpa.dao.StudentDao; import com.huongdanjava.springmvcjpa.entity.Student; @Repository public class StudentDaoImpl implements StudentDao { @PersistenceContext private EntityManager em; @Override public Student findById(Long id) { return em.find(Student.class, id); } } |
Trong class này mình đã inject đối tượng EntityManager từ Spring container bằng annotation @PersistenceContext. Và chúng ta sẽ sử dụng đối tượng EntityManager này để tìm kiếm sinh viên theo ID.
Để Controller có thể thao tác với database, mình sẽ tạo mới interface StudentService và implementation của nó là StudentServiceImpl cho Controller sử dụng.
Interface StudentService có nội dung như sau:
1 2 3 4 5 6 7 8 9 |
package com.huongdanjava.springmvcjpa.service; import com.huongdanjava.springmvcjpa.web.entity.Student; public interface StudentService { Student findById(Long id); } |
Và class implementation StudentServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
package com.huongdanjava.springmvcjpa.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.huongdanjava.springmvcjpa.dao.StudentDao; import com.huongdanjava.springmvcjpa.entity.Student; import com.huongdanjava.springmvcjpa.service.StudentService; @Service public class StudentServiceImpl implements StudentService { @Autowired private StudentDao studentDao; @Override public com.huongdanjava.springmvcjpa.web.entity.Student findById(Long id) { Student studentFromDb = studentDao.findById(id); if (studentFromDb == null) { return null; } com.huongdanjava.springmvcjpa.web.entity.Student student = new com.huongdanjava.springmvcjpa.web.entity.Student(); student.setName(studentFromDb.getName()); return student; } } |
Ở đây, mình đã khai báo class StudentServiceImpl sử dụng annotation @Service và mình cũng đã inject bean StudentDao vào đối tượng StudentServiceImpl để thao tác với database.
Ở đây, mình còn sử dụng một đối tượng Student khác, chỉ sử dụng ở level Service và Controller. Việc tách bạch giữa đối tượng Student cho database và đối tượng Student cho level Controller sẽ giúp chúng ta dễ chỉnh sửa sau này.
Nội dung của class com.huongdanjava.springmvcjpa.web.entity.Student như sau:
1 2 3 4 5 6 7 8 9 |
package com.huongdanjava.springmvcjpa.web.entity; import lombok.Data; @Data public class Student { private String name; } |
Bây giờ thì chúng ta sẽ sử dụng bean StudentService trong Controller để nó có thể thông qua đối tượng này thao tác với database.
Mình sẽ chỉnh sửa class HomeController sử dụng StudentService để tìm thông tin của sinh viên theo ID như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
package com.huongdanjava.springmvcjpa; import java.text.DateFormat; import java.util.Date; import java.util.Locale; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.huongdanjava.springmvcjpa.service.StudentService; /** * Handles requests for the application home page. */ @Controller public class HomeController { private static final Logger logger = LoggerFactory.getLogger(HomeController.class); @Autowired private StudentService studentService; /** * Simply selects the home view to render by returning its name. */ @RequestMapping(value = "/", method = RequestMethod.GET) public String home(Locale locale, Model model) { logger.info("Welcome home! The client locale is {}.", locale); Date date = new Date(); DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale); String formattedDate = dateFormat.format(date); model.addAttribute("serverTime", formattedDate ); model.addAttribute("student", studentService.findById(Long.valueOf(2))); return "home"; } } |
Sau khi có kết quả, mình sẽ đưa nó vào Model với key là “student” để hiển thị trên View như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page session="false" %> <html> <head> <title>Home</title> </head> <body> <h1> Hello world! </h1> <P> The time on the server is ${serverTime}. </P> My name is ${student.name} </body> </html> |
Vậy là chúng ta đã hoàn thành các bước cần thiết rồi đó các bạn. Giờ thì chạy lên thôi!
Giả sử database mình đang có dữ liệu như sau:
thì khi chạy kết quả sẽ như sau:
Thắng
a ơi !! cho e hỏi đoạn StudentServiceImpl inject Interface StudentDAO , thế sao n lại dùng được phương thức trong class StudentDAOImpl ạ ???
Khanh Nguyen
Vì StudentDAOImpl là implement của interface StudentDAO đó em. Em nên tìm hiểu thêm về Spring framework em nhé!
duong
anh cho em hỏi:
ở class ” StudentDaoImpl implement StudentDao” có thuộc tính private ” EntityManager em;” dùng @PersistenceContext nhưng trong spring container em không thấy có khai báo bean nào loại EntityManager được khai báo, là sao ạ. Anh giải thích giúp em với.
thuong_java
mình bị như này là gì vậy ad
No qualifying bean of type ‘com.huongdanjava.springmvcjpa.service.StudentService’ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
Khanh Nguyen
Không có implementation của class StudentService được khai báo trong Spring container đó em.
X981
Viết bài về Spring Data Jpa đi bạn. Tks bạn.
Khanh Nguyen
Đang viết nha bạn! Cứ tạo issue trên GitHub như vậy là mình nhớ nha bạn. 😀
Khanh Nguyen
Xem bài viết ở đây nhé bạn https://huongdanjava.com/vi/tong-quan-ve-spring-data-jpa.html
Trương Quốc Chiến
Chào Khánh. Bạn có thể giải thích hoặc đưa ra 1 trường hợp nhặp nhằn khi không sử dụng lớp DTO (student cho controller) như bạn nói ở trên không nhỉ ?
Khanh Nguyen
Hi bạn,
Việc tách bạch vậy sẽ có ích rất nhiều trong những ứng dụng lớn. Việc thay đổi trong database sẽ không ảnh hưởng nhiều với controller và ngược lại.
Ví dụ như bây giờ mình cần thêm một cột trong table student trong database để phục vụ cho 1 request khác. Nếu mà controller mình dùng entity của table này, bạn có thể thấy nó sẽ ảnh hưởng đến kết quả trả về của request. Một sự thay đổi nhỏ trong database, sẽ ảnh hưởng đến rất nhiều cho hệ thống, bạn phải sửa tất cả những thứ liên quan.
Hi vọng là bạn hình dung được.
Trương Quốc Chiến
Chào Khánh.
Mình xin chân thành cảm ơn câu trả lời của bạn. Bạn đã gỡ khuất mắt bấy lâu nay của mình. Mình xin cảm ơn bạn 1 lần nữa 🙂