post-image

[Thực hành] Ứng dụng quản lý khách hàng

6. JPA

Mục tiêu

Luyện tập việc sử dụng Spring JPA với Hibernate.

Điều kiện

Có kiến thức căn bản về việc sử dụng Spring JPA với Hibernate.

Mô tả

Trong phần này, chúng ta sẽ phát triển một ứng dụng quản lý khách hàng, sử dụng JPA và Hibernate.

Ứng dụng có các chức năng chính:

  • Hiển thị danh sách khách hàng
  • Thêm một khách hàng mới
  • Xoá một khách hàng
  • Chỉnh sửa thông tin khách hàng

Hướng dẫn

Bước 1: Tạo dự án mới

New project > Gradle, chọn Java và Web

Đặt GroupId là com.codegym và ArtifactId là customer-management

Bước 2: Thêm các thư viện cần thiết vào file build.gradle

Danh sách các thư viện:

compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0'
compile group: 'org.springframework', name: 'spring-core', version: '4.3.17.RELEASE'
compile group: 'org.springframework', name: 'spring-context', version: '4.3.17.RELEASE'
compile group: 'org.springframework', name: 'spring-beans', version: '4.3.17.RELEASE'
compile group: 'org.springframework', name: 'spring-web', version: '4.3.17.RELEASE'
compile group: 'org.hibernate', name: 'hibernate-core', version: '5.3.0.Final'
compile group: 'org.hibernate', name: 'hibernate-entitymanager', version: '5.3.0.Final'
compile group: 'org.springframework', name: 'spring-orm', version: '4.3.17.RELEASE'

compile group: 'org.springframework', name: 'spring-webmvc', version: '4.3.17.RELEASE'
compile group: 'org.thymeleaf', name: 'thymeleaf-spring4', version: '3.0.9.RELEASE'
compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.11'
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '1.11.12.RELEASE'

Bước 3: Tạo cấu trúc của ứng dụng

Các gói cơ bản bao gồm:

com.codegym.cms.model

com.codegym.cms.repository

com.codegym.cms.service

com.codegym.cms.controller

Lớp ApplicationInitializer là lớp cấu hình khởi tạo cho ứng dụng (thay thế file web.xml nếu dùng cấu hình .xml):

package com.codegym.cms;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{ApplicationConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[0];
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

Bước 4: Tạo lớp cấu hình cho ứng dụng

Lớp ApplicationConfig được dùng để cấu hình cho toàn bộ ứng dụng (thay thế cho file dispatcher-servlet.xml nếu dùng cấu hình .xml)

package com.codegym.cms
import com.codegym.cms.repository.CustomerRepository;
import com.codegym.cms.repository.impl.CustomerRepositoryImpl;
import com.codegym.cms.service.CustomerService;
import com.codegym.cms.service.impl.CustomerServiecImpl;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan("com.codegym.cms")
public class ApplicationConfig extends WebMvcConfigurerAdapter implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Bean
    public CustomerRepository customerRepository(){
        return new CustomerRepositoryImpl();
    }

    @Bean
    public CustomerService customerService(){
        return new CustomerServiecImpl();
    }


    //Thymeleaf Configuration
    @Bean
    public SpringResourceTemplateResolver templateResolver(){
        SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
        templateResolver.setApplicationContext(applicationContext);
        templateResolver.setPrefix("/WEB-INF/views");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        return templateResolver;
    }

    @Bean
    public TemplateEngine templateEngine(){
        TemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver());
        return templateEngine;
    }

    @Bean
    public ThymeleafViewResolver viewResolver(){
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(templateEngine());
        return viewResolver;
    }

    //JPA configuration
    @Bean
    @Qualifier(value = "entityManager")
    public EntityManager entityManager(EntityManagerFactory entityManagerFactory) {
        return entityManagerFactory.createEntityManager();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource());
        em.setPackagesToScan(new String[]{"com.codegym.cms.model"});

        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        em.setJpaProperties(additionalProperties());
        return em;
    }

    @Bean
    public DataSource dataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/cms");
        dataSource.setUsername( "root" );
        dataSource.setPassword( "123456" );
        return dataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        return transactionManager;
    }

    Properties additionalProperties() {
        Properties properties = new Properties();
        properties.setProperty("hibernate.hbm2ddl.auto", "update");
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
        return properties;
    }

}

Bước 5: Cấu hình Tomcat Server

Chạy thử ứng dụng để đảm bảo các bước cài đặt và cấu hình đã hoạt động tốt.

Lưu ý: Nếu xuất hiện lỗi  java.lang.ClassNotFoundException:org.springframework.web.context.ContextLoaderListener thì cần xử lý bằng cách đi vào Project Structure -> Artifactts -> Put into Output Root để thêm tất cả các thư viện vào trong output khi build.

Bước 6: Tạo lớp model Customer

Lớp com.codegym.cms.model.Customer

package com.codegym.cms.model;

import javax.persistence.*;

@Entity
@Table(name = "customers")
public class Customer {

    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private Long id;
    private String firstName;
    private String lastName;

    public Customer() {}

    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

Bước 7: Tạo repository

Interface Repository khai báo các phương thức chung cho tất cả các repository.





package com.codegym.cms.repository;

import java.util.List;

public interface Repository <T> {
    List<T> findAll();

    T findById(Long id);

    void save(T model);

    void remove(Long id);
}

Interface CustomerRepository khai báo các phương thức riêng cho Customer, trong trường hợp này, CustomerRepository không có phương thức tuỳ biến nào cả, do đó chỉ đơn giản là kế thừa từ interface Repository.

package com.codegym.cms.repository;

import com.codegym.cms.model.Customer;

public interface CustomerRepository extends Repository<Customer> {
}
Lớp com.codegym.cms.repository.impl.CustomerRepositoryImpl:

package com.codegym.cms.repository.impl;

import com.codegym.cms.model.Customer;
import com.codegym.cms.repository.CustomerRepository;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.transaction.Transactional;
import java.util.List;

@Transactional
public class CustomerRepositoryImpl implements CustomerRepository {

    @PersistenceContext
    private EntityManager em;

    @Override
    public List<Customer> findAll() {
        TypedQuery<Customer> query = em.createQuery("select c from Customer c", Customer.class);
        return query.getResultList();
    }

    @Override
    public Customer findById(Long id) {
        TypedQuery<Customer> query = em.createQuery("select c from Customer c where  c.id=:id", Customer.class);
        query.setParameter("id", id);
        try {
            return query.getSingleResult();
        }catch (NoResultException e){
            return null;
        }
    }

    @Override
    public void save(Customer model) {
        if(model.getId() != null){
            em.merge(model);
        } else {
            em.persist(model);
        }
    }

    @Override
    public void remove(Long id) {
        Customer customer = findById(id);
        if(customer != null){
            em.remove(customer);
        }
    }
}

Bước 8: Tạo các service

Interface com.codegym.cms.service.CustomerService

package com.codegym.cms.service;

import com.codegym.cms.model.Customer;

import java.util.List;

public interface CustomerService {
    List<Customer> findAll();

    Customer findById(Long id);

    void save(Customer customer);

    void remove(Long id);
}
Lớp com.codegym.cms.service.impl.CustomerServiceImpl

package com.codegym.cms.service.impl;

import com.codegym.cms.model.Customer;
import com.codegym.cms.repository.CustomerRepository;
import com.codegym.cms.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

public class CustomerServiecImpl implements CustomerService{

    @Autowired
    private CustomerRepository customerRepository;

    @Override
    public List<Customer> findAll() {
        return customerRepository.findAll();
    }

    @Override
    public Customer findById(Long id) {
        return customerRepository.findById(id);
    }

    @Override
    public void save(Customer customer) {
        customerRepository.save(customer);
    }

    @Override
    public void remove(Long id) {
        customerRepository.remove(id);
    }
}

Bước 9: Khai báo các service và repository bean trong ApplicationConfig

Bổ sung khai báo 2 bean mới vào trong lớp ApplicationConfig

@Bean
public CustomerRepository customerRepository(){
    return new CustomerRepositoryImpl();
}

@Bean
public CustomerService customerService(){
    return new CustomerServiecImpl();
}

Bước 10: Tạo CustomerController

Tạo lớp com.codegym.cms.controller.CustomerController với phương thức để hiển thị form tạo Customer và lưu Customer.

package com.codegym.cms.controller;

import com.codegym.cms.model.Customer;
import com.codegym.cms.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class CustomerController {

    @Autowired
    private CustomerService customerService;

    @GetMapping("/create-customer")
    public ModelAndView showCreateForm(){
        ModelAndView modelAndView = new ModelAndView("/customer/create");
        modelAndView.addObject("customer", new Customer());
        return modelAndView;
    }

    @PostMapping("/create-customer")
    public ModelAndView saveCustomer(@ModelAttribute("customer") Customer customer){
        customerService.save(customer);
        ModelAndView modelAndView = new ModelAndView("/customer/create");
        modelAndView.addObject("customer", new Customer());
        modelAndView.addObject("message", "New customer created successfully");
        return modelAndView;
    }
}

Bước 11: Tạo view để thêm Customer mới

File /views/customer/create.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Create customer</title>
</head>
<body>
    <h1>Create customer</h1>
    <p>
        <a href="/customers">Customer list</a>
    </p>
    <th:block th:if="${message}">
        <p th:text="${message}"></p>
    </th:block>
    <form th:action="@{/create-customer}" th:object="${customer}" method="post">
        <table>
            <tr>
                <td>First name:</td>
                <td><input type="text" th:field="*{firstName}"/></td>
            </tr>
            <tr>
                <td>Last name:</td>
                <td><input type="text" th:field="*{lastName}"/></td>
            </tr>
            <tr>
                <td></td>
                <td><input type="submit" value="Create customer"></td>
            </tr>
        </table>
    </form>
</body>
</html>

Bước 12: Chạy ứng dụng và quan sát kết quả

Đi đến đường dẫn http://localhost:8080/create-customer và nhập dữ liệu của Customer.

Bước 13: Tạo chức năng hiển thị danh sách khách hàng

Cập nhật CustomerController

@GetMapping("/customers")
public ModelAndView listCustomers(){
    List<Customer> customers = customerService.findAll();
    ModelAndView modelAndView = new ModelAndView("/customer/list");
    modelAndView.addObject("customers", customers);
    return modelAndView;
}
Tạo file /WEB-INF/views/customer/list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <a href="/create-customer">Create new customer</a>
<h1>Customers</h1>
<table border="1">
    <tr>
        <th>First name</th>
        <th>Last name</th>
    </tr>
    <th:block th:each="customer : ${customers}">
        <tr>
            <td th:text="${customer.firstName}"></td>
            <td th:text="${customer.lastName}"></td>
        </tr>
    </th:block>
</table>
</body>
</html>

Chạy ứng dụng và đi đến trang http://localhost:8080/customers để quan sát kết quả.

Bước 14: Tạo chức năng cập nhật Customer

Cập nhật CustomerController





@GetMapping("/edit-customer/{id}")
public ModelAndView showEditForm(@PathVariable Long id){
    Customer customer = customerService.findById(id);
    if(customer != null) {
        ModelAndView modelAndView = new ModelAndView("/customer/edit");
        modelAndView.addObject("customer", customer);
        return modelAndView;

    }else {
        ModelAndView modelAndView = new ModelAndView("/error.404");
        return modelAndView;
    }
}

@PostMapping("/edit-customer")
public ModelAndView updateCustomer(@ModelAttribute("customer") Customer customer){
    customerService.save(customer);
    ModelAndView modelAndView = new ModelAndView("/customer/edit");
    modelAndView.addObject("customer", customer);
    modelAndView.addObject("message", "Customer updated successfully");
    return modelAndView;
}

Tạo file /WEB-INF/views/customer/edit.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Edit customer</title>
</head>
<body>
    <h1>Edit customer</h1>
    <p>
        <a href="/customers">Customer list</a>
    </p>
    <th:block th:if="${message}">
        <p th:text="${message}"></p>
    </th:block>
    <form th:action="@{/edit-customer}" th:object="${customer}" method="post">
        <input th:type="hidden" name="id" th:field="*{id}">
        <table>
            <tr>
                <td>First name:</td>
                <td><input type="text" th:field="*{firstName}"/></td>
            </tr>
            <tr>
                <td>Last name:</td>
                <td><input type="text" th:field="*{lastName}"/></td>
            </tr>
            <tr>
                <td></td>
                <td><input type="submit" value="Update customer"></td>
            </tr>
        </table>
    </form>
</body>
</html>

Cập nhật file /WEB-INF/views/customer/list.html, thêm đường link đến trang edit.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <a href="/create-customer">Create new customer</a>
<h1>Customers</h1>
<table border="1">
    <tr>
        <th>First name</th>
        <th>Last name</th>
        <th>Edit</th>
    </tr>
    <th:block th:each="customer : ${customers}">
        <tr>
            <td th:text="${customer.firstName}"></td>
            <td th:text="${customer.lastName}"></td>
            <td><a th:href="@{/edit-customer/__${customer.id}__ }">Edit</a></td>
        </tr>
    </th:block>
</table>
</body>
</html>

Chạy ứng dụng và quan sát kết quả.

Bước 15: Tạo chức năng xoá Customer

Cập nhật CustomerController

@GetMapping("/delete-customer/{id}")
public ModelAndView showDeleteForm(@PathVariable Long id){
    Customer customer = customerService.findById(id);
    if(customer != null) {
        ModelAndView modelAndView = new ModelAndView("/customer/delete");
        modelAndView.addObject("customer", customer);
        return modelAndView;

    }else {
        ModelAndView modelAndView = new ModelAndView("/error.404");
        return modelAndView;
    }
}

@PostMapping("/delete-customer")
public String deleteCustomer(@ModelAttribute("customer") Customer customer){
    customerService.remove(customer.getId());
    return "redirect:customers";
}
Tạo file /WEB-INF/views/customer/delete.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Delete customer</title>
</head>
<body>
    <h1>Delete customer</h1>
    <h2>Are you sure?</h2>
    <p>
        <a href="/customers">Customer list</a>
    </p>
    <form th:action="@{/delete-customer}" th:object="${customer}" method="post">
        <input th:type="hidden" name="id" th:field="*{id}">
        <table>
            <tr>
                <td>First name:</td>
                <td th:text="${customer.firstName}"></td>
            </tr>
            <tr>
                <td>Last name:</td>
                <td th:text="${customer.lastName}"></td>
            </tr>
            <tr>
                <td></td>
                <td><input type="submit" value="Delete customer"></td>
            </tr>
        </table>
    </form>
</body>
</html>
Cập nhật file /WEB-INF/views/customer/list.html, thêm đường link đến trang delete.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <a href="/create-customer">Create new customer</a>
<h1>Customers</h1>
<table border="1">
    <tr>
        <th>First name</th>
        <th>Last name</th>
        <th>Edit</th>
        <th>Delete</th>
    </tr>
    <th:block th:each="customer : ${customers}">
        <tr>
            <td th:text="${customer.firstName}"></td>
            <td th:text="${customer.lastName}"></td>
            <td><a th:href="@{/edit-customer/__${customer.id}__ }">Edit</a></td>
            <td><a th:href="@{/delete-customer/__${customer.id}__ }">Delete</a></td>
        </tr>
    </th:block>
</table>
</body>
</html>

Chạy ứng dụng và quan sát kết quả.

Mã nguồn tham khảo: https://github.com/codegym-vn/spring-jpa-customer-management

Hướng dẫn nộp bài:

Up code lên github

Paste link github vào phần nộp bài và nhấn Submit

Trả lời

Email của bạn sẽ không được hiển thị công khai.