Để remind kiến thức cũng như tổng kết khi mới bắt đầu học một công nghệ mới, mình thường tạo một app đơn giản có đủ các thao tác thêm, xóa, sửa (CRUD). Bài viết này là một tutorial dạng cơ bản nhất khi học về Spring, sử dụng các công nghệ sau:

  • Spring Boot: để khởi tạo và cấu hình ứng dụng một cách nhanh chóng.
  • Spring MVC: để xây dựng app.
  • Spring Data: cụ thể là Spring Data JPA, dùng để giúp ứng dụng thao tác với tầng cơ sở dữ liệu.
  • Hệ quản trị cơ sở dữ liệu: MySQL.
  • Template engine: Thymeleaf và Bootstrap css.
  • Tool IDE: Intellij JetBrain.
  • Công nghệ khác: Maven 3, 1. Tomcat Embed 8.
  • Source code dự án: https://github.com/tubean/spring-boot-application

Các chức năng chính sẽ xây dựng:

  • Hiển thị quản lý user.
  • Thêm user.
  • Sửa user.
  • Xóa user.

Cơ sở dữ liệu cần chuẩn bị: Bảng user:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  `email` varchar(45) NOT NULL,
  `phone` varchar(45) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

1. Khởi tạo dự án bằng Intellij

Đầu tiên chúng ta khởi động intellij, chọn new project -> Spring Initializr khởi tạo dự án

  • Chọn Next, điền các thông tin Group, Artifact, Name, Description

thông tin cơ bản

  • Chọn tiếp các dependencies cần thiết: Web, Thymeleaf, MySQL, JPA:

dependencies

  • Đặt tên project và finish bước khởi tạo.

2. Cấu trúc dự án

Cây thư mục dự án như sau:

structure

  • Để sử dụng bootstrap css, các bạn tải bootstrap , sau đó giải nén và copy file bootstrap.min.css vào thư mục resources/static.
  • Kiểm tra cấu hình file pom.xml:
<parent>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-parent</artifactId>  
    <version>2.1.0.RELEASE</version>  
    <relativePath/> <!-- lookup parent from repository -->  
</parent>  

<properties>  
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>  
    <java.version>1.8</java.version>  
</properties>  

<dependencies>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-data-jpa</artifactId>  
    </dependency>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-thymeleaf</artifactId>  
    </dependency>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-web</artifactId>  
    </dependency>  

    <dependency>  
        <groupId>mysql</groupId>  
        <artifactId>mysql-connector-java</artifactId>  
        <scope>runtime</scope>  
    </dependency>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-test</artifactId>  
        <scope>test</scope>  
    </dependency>  
</dependencies>

3. Xây dựng view

  • Nếu sử dụng Spring MVC và Thymeleaf, chúng ta sẽ cần config khá nhiều thứ mới có thể sử dụng engine Thymeleaf. Tuy nhiên sử dụng Spring Boot thì chúng ta không cần quan tâm nhiều đến thế. Các file html được Spring Boot chỉ định đặt trong folder src/main/resources nên chúng ta sẽ bỏ các file layout vào trong này.
  • Để không phải relaunch server để xem các thay đổi trên giao diện, các bạn vào file application.properties thêm cấu hình sau: xml spring.thymeleaf.cache=false

Quản lý resources

Các file static resources (css, js, ảnh) sẽ được Spring Boot đọc từ thư mục static trong src/main/resources. Để cho tiện quản lý:

  • Các file css được đặt trong static/css
  • Các file js được đặt trong static/js
  • Các file ảnh được đặt trong static/images

Trong dự án này do chỉ sử dụng một file bootstrap.min.css nên mình không tạo thêm các folder khác 😄.

Xây dựng layout

Trong ứng dụng này sẽ có 3 màn hình chính là:

  • Màn hình hiển thị user: index.html
  • Thêm mới một user: addUser.html
  • Sửa một user: editUser.html

Nội dung các file như sau:

index.html

<!DOCTYPE html>  
<html lang="en" xmlns:th="http://www.springframework.org/schema/data/jaxb">  
<head>  
    <title>User List</title>  
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>  
    <link type="text/css" rel="stylesheet" href="bootstrap.min.css" th:href="@{bootstrap.min.css}"/>  
</head>  
<body>  
<div class="col-3"></div>  
<div class="container col-6">  
    <h1>Manage User</h1>  
    <table class="table table-active">  
        <tr>  
            <th>Name</th>  
            <th>Email</th>  
            <th>Phone</th>  
            <th>Actions</th>  
        </tr>  
        <tr th:each="user : ${users}">  
            <td th:text="${user.name}"></td>  
            <td th:text="${user.email}"></td>  
            <td th:text="${user.phone}"></td>  
            <td>  
                <a th:href="@{/edit?id={id}(id=${user.id})}" class="btn btn-primary">Edit</a>  
                <a th:href="@{/delete?id={id}(id=${user.id})}" class="btn btn-danger">Delete</a>  
            </td>  
        </tr>  
    </table>  
    <a href="/add" class="btn btn-success">Add User</a>  
</div>  
</body>  
</html>

addUser.html

<!DOCTYPE html>  
<html lang="en" xmlns:th="http://www.springframework.org/schema/data/jaxb">  
<head>  
    <meta charset="UTF-8">  
    <title>Add User</title>  
    <link type="text/css" rel="stylesheet" href="bootstrap.min.css" th:href="@{bootstrap.min.css}"/>  
</head>  
<body>  
<div class="col-3"></div>  
<div class="container col-6">  
    <h1>New User</h1>  
    <div class="col-md-auto">  
        <form th:object="${user}" th:action="@{save}" action="#" method="post">  
            <div>  
                <label th:text="Name" th:for="name"></label>  
                <input type="text" th:field="*{name}" class="form-control" placeholder="Name"/>  
            </div>  
            <div style="clear: both; display: block; height: 10px"></div>  
            <div>  
                <label th:text="Email" th:for="email"></label>  
                <input type="text" th:field="*{email}" class="form-control" placeholder="Email"/>  
            </div>  
            <div style="clear: both; display: block; height: 10px"></div>  
            <div>  
                <label th:text="Phone" th:for="phone"></label>  
                <input type="text" th:field="*{phone}" class="form-control" placeholder="Phone"/>  
            </div>  
            <div style="clear: both; display: block; height: 10px"></div>  
            <input type="submit" class="btn btn-success" value="Save"/>  
        </form>  
    </div>  
</div>  
</body>  
</html>

editUser

<!DOCTYPE html>  
<html lang="en" xmlns:th="http://www.springframework.org/schema/data/jaxb">  
<head>  
    <meta charset="UTF-8">  
    <title>Edit User</title>  
    <link type="text/css" rel="stylesheet" href="bootstrap.min.css" th:href="@{bootstrap.min.css}"/>  
</head>  
<body>  
<div class="col-3"></div>  
<div class="container col-6">  
    <h1>Edit User</h1>  
    <form th:object="${user}" th:action="@{save}" action="#" method="post">  
        <input type="hidden" th:field="*{id}">  
        <div>  
            <label th:text="Name" th:for="name"></label>  
            <input type="text" th:field="*{name}" class="form-control"/>  
        </div>  
        <div style="clear: both; display: block; height: 10px"></div>  
        <div>  
            <label th:text="Email" th:for="email"></label>  
            <input type="text" th:field="*{email}" class="form-control"/>  
        </div>  
        <div style="clear: both; display: block; height: 10px"></div>  
        <div>  
            <label th:text="Phone" th:for="phone"></label>  
            <input type="text" th:field="*{phone}" class="form-control"/>  
        </div>  
        <div style="clear: both; display: block; height: 10px"></div>  
        <input type="submit" class="btn btn-success" value="Save"/>  
    </form>  
</div>  
</body>  
</html>

Chú ý khi khai báo file css trong layout: - Thuộc tính href là của HTML5, cung cấp đường dẫn tới file style.css cho trình duyệt nếu như server chưa run. Cho nên chúng ta hoàn toàn có thể mở file html để xem dù chưa run server. - Thuộc tính th:href là của Thymeleaf, cung cấp đường dẫn tới file style.css cho trình duyệt khi server run. @{} mà một biểu thức SPeL xác định đường dẫn.

4. Xây dựng DataAccess layer và Service layer

Cấu hình DataSource, JPA

Các cấu hình liên quan đến DataSource, JPA chúng ta sẽ viết trong file application.properties của java/main/resources. Chúng ta có thể cấu hình đơn giản như sau:

spring.datasource.url=jdbc:mysql://localhost:3306/spring_app  
spring.datasource.username=root  
spring.datasource.password=  
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

Chú ý thay đổi các giá trị url, username, password cho phù hợp với môi trường của bạn.

Entity

Đây là các java bean được ánh xạ từ các bảng trong cơ sở dữ liệu. Với chỉ duy nhất một bảng user, mình sẽ tạo một class User trong package entity:

package io.github.tubean.myspringcrud.entity;  

import javax.persistence.*;  

@Entity  
@Table(name = "user")
public class User {  
  @Id  
 @GeneratedValue(strategy = GenerationType.AUTO)  
  private Long id;  

  @Column(name = "name")  
  private String name;  

  @Column(name = "email")  
  private String email;  

  @Column(name = "phone")  
  private String phone;  

  public User() {}  

  public User(String name, String email, String phone) {  
    this.name = name;  
    this.email = email;  
    this.phone = phone;  
  }  

  public Long getId() {  
    return id;  
  }  

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

  public String getName() {  
    return name;  
  }  

  public void setName(String name) {  
    this.name = name;  
  }  

  public String getEmail() {  
    return email;  
  }  

  public void setEmail(String email) {  
    this.email = email;  
  }  

  public String getPhone() {  
    return phone;  
  }  

  public void setPhone(String phone) {  
    this.phone = phone;  
  }  
}

Các annotation mình sử dụng trong đoạn code trên là các annotation của JPA:

  • @Entity xác định lớp hiện tại là một entity.
  • @Table xác định tên bảng ánh xạ sang.
  • @Id xác định thuộc tính hiện tại là ID trong bảng CSDL.
  • @GeneratedValue xác định kiểu sinh khóa chính, ở đây là AUTO_INCREMENT.
  • @Column xác định thuộc tính hiện tại là một cột trong CSDL.

Repository

Đây là các interface giúp chúng ta thao tác với CSDL. Trong Spring Data JPA có một tính năng rất hay đó là CRUD Repository. Đây là một interface cung cấp các phương thức CRUD cơ bản. Chúng ta chỉ cần định nghĩa một interface kế thừa CRUD Repository, Spring Data JPA sẽ dùng các generic và reflection để sinh implementation tương ứng với interface đó. Trong dự án này chúng ta tạo một interface UserRepository:

package io.github.tubean.myspringcrud.repository;  

import io.github.tubean.myspringcrud.entity.User;  
import org.springframework.data.repository.CrudRepository;  
import org.springframework.stereotype.Repository;  

@Repository  
public interface UserRepository extends CrudRepository<User, Long> {}

Các tham số trong CrudRepository là tên entity (User) và kiểu làm khóa chính (Long).

Service

Trong mô hình MVC thì nơi xử lý các business logic được đặt trong package service. Chúng ta có một interface UserService :

package io.github.tubean.myspringcrud.service;  

import io.github.tubean.myspringcrud.entity.User;  

import java.util.List;  
import java.util.Optional;  

public interface UserService {  
  List<User> getAllUser();  

  void saveUser(User user);  

  void deleteUser(Long id);  

  Optional<User> findUserById(Long id);  
}

và một implementation UserServiceImpl của nó:

package io.github.tubean.myspringcrud.service.impl;  

import io.github.tubean.myspringcrud.entity.User;  
import io.github.tubean.myspringcrud.repository.UserRepository;  
import io.github.tubean.myspringcrud.service.UserService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  

import java.util.List;  
import java.util.Optional;  

@Service  
public class UserServiceImpl implements UserService {  
  @Autowired private UserRepository userRepository;  

  @Override  
  public List<User> getAllUser() {  
    return (List<User>) userRepository.findAll();  
  }  

  @Override  
  public void saveUser(User user) {  
    userRepository.save(user);  
  }  

  @Override  
  public void deleteUser(Long id) {  
    userRepository.deleteById(id);  
  }  

  @Override  
  public Optional<User> findUserById(Long id) {  
    return userRepository.findById(id);  
  }  
}

Trong đó: - Annotation @Service giúp Spring xác định lớp hiện tại là một Service và tạo một bean cho lớp đó. - Annotation @Autowired dùng để inject UserRepository vào UserServiceImpl.

5. Tạo controller để điều hướng các request đến business và view tương ứng:

Chúng ta tạo một controller UserController trong package controller:

package io.github.tubean.myspringcrud.controller;  

import io.github.tubean.myspringcrud.entity.User;  
import io.github.tubean.myspringcrud.service.UserService;  
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 org.springframework.web.bind.annotation.RequestParam;  

import java.util.List;  
import java.util.Optional;  

@Controller  
public class UserController {  
  @Autowired private UserService userService;  

  @RequestMapping("/")  
  public String index(Model model) {  
    List<User> users = userService.getAllUser();  

    model.addAttribute("users", users);  

    return "index";  
  }  

  @RequestMapping(value = "add")  
  public String addUser(Model model) {  
    model.addAttribute("user", new User());  
    return "addUser";  
  }  

  @RequestMapping(value = "/edit", method = RequestMethod.GET)  
  public String editUser(@RequestParam("id") Long userId, Model model) {  
    Optional<User> userEdit = userService.findUserById(userId);  
    userEdit.ifPresent(user -> model.addAttribute("user", user));  
    return "editUser";  
  }  

  @RequestMapping(value = "save", method = RequestMethod.POST)  
  public String save(User user) {  
    userService.saveUser(user);  
    return "redirect:/";  
  }  

  @RequestMapping(value = "/delete", method = RequestMethod.GET)  
  public String deleteUser(@RequestParam("id") Long userId, Model model) {  
    userService.deleteUser(userId);  
    return "redirect:/";  
  }  
}

Trong đó:

  • Annotation @Controller giúp Spring xác định lớp hiện tại là một Controller.
  • Annotation RequestMapping(/) xác định phương thức index() sẽ đón nhận các request có HTTP method là GET và URI pattern là /.
  • Annotation @Autowired inject ContactService vào ContactController
  • Phương thức index() được truyền vào 1 tham số có kiểu dữ liệu là Model. Model có nhiệm vụ truyền dữ liệu từ Controller cho View. Ở đây mình sẽ lấy ra danh sách các user thông qua userService.getAllUser(). Sau đó gắn danh sách này vào Model thông qua phương thức addAttribute(), users chính là tên biến đại diện cho danh sách mà ta sẽ dùng ở View sau này.

  • Phương thức index() trả về 1 String, từ String này Spring sẽ suy ra View nào sẽ nhận dữ liệu từ Controller: return "index";" => view là index.html


Cuối cùng start ứng dụng bằng intellij và truy cập vào đường dẫn http://localhost:8080/ để thao tác với ứng dụng.

Mục tiêu của bài viết là để remind và hướng dẫn tạo một ứng dụng nhanh gọn bằng Spring Boot nên có nhiều phần mình không giải thích rõ ràng. Để tìm hiểu chi tiết các kiến thức được sử dụng một cách đầy đủ hơn, các bạn có thể tham khảo thêm bài viết dưới đây: https://kipalog.com/posts/Lap-trinh-Spring-voi-ung-dung-MyContact.