Spring Boot CRUD Web Application with Thymeleaf, Spring MVC, Spring Data JPA, Hibernate, MySQL

In this tutorial, we'll learn how to develop a Spring MVC CRUD web application with Spring Boot, Thymeleaf, Spring Data JPA, Hibernate, and MySQL database.

This Spring Boot project is upgraded to Spring Boot 3 and Java 17.
Learn spring boot at https://www.javaguides.net/p/spring-boot-tutorial.html.
Learn Hibernate at https://www.javaguides.net/p/hibernate-tutorial.html 
Learn Spring Data JPA at https://www.javaguides.net/p/spring-data-jpa-tutorial.html 

Video Tutorial

Check out the youtube video of this tutorial:

Project Requirements

Here is a high-level project requirement to create a web application for Employee Management System. Users should able to:
  • Get all the employees
  • Add a new employee
  • Update an employee
  • Delete an employee

What we will build?

We will create a Spring MVC web application for Employee Management System to implement the above features.

The below screenshot summaries CRUD operations that we are going to develop in this tutorial.

Application Flow

The below diagram shows the application flow of our Spring MVC web application with Thymeleaf:

Tools and technologies used

  • IDE - Eclipse / STS
  • Spring Boot 3+
  • Spring Framework 6+
  • Maven
  • Java 17
  • Spring Data JPA ( Hibernate)
  • Thymeleaf

1. Create Spring Boot Project

There are many ways to create a Spring Boot application. You can refer below articles to create a Spring Boot application.

2. Maven 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>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

3. Project Structure

4. Configure and Setup MySQL Database

Create a database with the name "demo" in the MySQL database server.
We’ll need to configure MySQL database URLusername, and password so that Spring can establish a connection with the database on startup. 
Open application.properties and add following MySQL database configuration:
# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
spring.datasource.username=root
spring.datasource.password=root

# Hibernate

# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE
Make sure that you change the spring.datasource.username and spring.datasource.password properties as per your MySQL installation.
The spring.jpa.hibernate.ddl-auto = update property makes sure that the database tables and the domain models in your application are in sync. Whenever you change the domain model, hibernate will automatically update the mapped table in the database when you restart the application.
I have also specified the log levels for hibernate so that we can debug the SQL queries executed by hibernate.

5. Model Layer - Create JPA Entity - Employee.java

Let's create Employee.java class under "net.javaguides.springboot.model" package and add following content to it:
package net.javaguides.springboot.model;

import jakarta.persistence.*;

@Entity
@Table(name = "employees")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

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

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

    @Column(name = "email")
    private String email;
    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;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
}

6. Repository Layer - EmployeeRepository.java

Create a EmployeeRepository interface under "net.javaguides.springboot.repository" package and add the following content to it:
package net.javaguides.springboot.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import net.javaguides.springboot.model.Employee;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long>{

}

7. Service Layer 

7.1 EmployeeService.java interface

The complete code:
package net.javaguides.springboot.service;

import java.util.List;

import net.javaguides.springboot.model.Employee;

public interface EmployeeService {
    List < Employee > getAllEmployees();
    void saveEmployee(Employee employee);
    Employee getEmployeeById(long id);
    void deleteEmployeeById(long id);
}

7.2. EmployeeServiceImpl class

The complete code:
package net.javaguides.springboot.service;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import net.javaguides.springboot.model.Employee;
import net.javaguides.springboot.repository.EmployeeRepository;

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public List < Employee > getAllEmployees() {
        return employeeRepository.findAll();
    }

    @Override
    public void saveEmployee(Employee employee) {
        this.employeeRepository.save(employee);
    }

    @Override
    public Employee getEmployeeById(long id) {
        Optional < Employee > optional = employeeRepository.findById(id);
        Employee employee = null;
        if (optional.isPresent()) {
            employee = optional.get();
        } else {
            throw new RuntimeException(" Employee not found for id :: " + id);
        }
        return employee;
    }

    @Override
    public void deleteEmployeeById(long id) {
        this.employeeRepository.deleteById(id);
    }
}

8. Controller Layer - EmployeeController.java

The complete code:
package net.javaguides.springboot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

import net.javaguides.springboot.model.Employee;
import net.javaguides.springboot.service.EmployeeService;

@Controller
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    // display list of employees
    @GetMapping("/")
    public String viewHomePage(Model model) {
        model.addAttribute("listEmployees", employeeService.getAllEmployees());
        return "index";
    }

    @GetMapping("/showNewEmployeeForm")
    public String showNewEmployeeForm(Model model) {
        // create model attribute to bind form data
        Employee employee = new Employee();
        model.addAttribute("employee", employee);
        return "new_employee";
    }

    @PostMapping("/saveEmployee")
    public String saveEmployee(@ModelAttribute("employee") Employee employee) {
        // save employee to database
        employeeService.saveEmployee(employee);
        return "redirect:/";
    }

    @GetMapping("/showFormForUpdate/{id}")
    public String showFormForUpdate(@PathVariable(value = "id") long id, Model model) {

        // get employee from the service
        Employee employee = employeeService.getEmployeeById(id);

        // set employee as a model attribute to pre-populate the form
        model.addAttribute("employee", employee);
        return "update_employee";
    }

    @GetMapping("/deleteEmployee/{id}")
    public String deleteEmployee(@PathVariable(value = "id") long id) {

        // call delete employee method 
        this.employeeService.deleteEmployeeById(id);
        return "redirect:/";
    }
}

9. View Layer 

9.1 index.html

Create new index.html file under "resources/templates" folder and add the following content to it:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="ISO-8859-1">
    <title>Employee Management System</title>

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

</head>

<body>

    <div class="container my-2">
        <h1>Employees List</h1>

        <a th:href="@{/showNewEmployeeForm}" class="btn btn-primary btn-sm mb-3"> Add Employee </a>

        <table border="1" class="table table-striped table-responsive-md">
            <thead>
                <tr>
                    <th>Employee First Name</th>
                    <th>Employee Last Name</th>
                    <th>Employee Email</th>
                    <th> Actions </th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="employee : ${listEmployees}">
                    <td th:text="${employee.firstName}"></td>
                    <td th:text="${employee.lastName}"></td>
                    <td th:text="${employee.email}"></td>
                    <td> <a th:href="@{/showFormForUpdate/{id}(id=${employee.id})}" class="btn btn-primary">Update</a>
                        <a th:href="@{/deleteEmployee/{id}(id=${employee.id})}" class="btn btn-danger">Delete</a>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</body>

</html>

9.2 new_employee.html

Create new new_employee.html file under "resources/templates" folder and add the following content to it:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="ISO-8859-1">
    <title>Employee Management System</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
</head>

<body>
    <div class="container">
        <h1>Employee Management System</h1>
        <hr>
        <h2>Save Employee</h2>

        <form action="#" th:action="@{/saveEmployee}" th:object="${employee}" method="POST">
            <input type="text" th:field="*{firstName}" placeholder="Employee First Name" class="form-control mb-4 col-4">

            <input type="text" th:field="*{lastName}" placeholder="Employee Last Name" class="form-control mb-4 col-4">

            <input type="text" th:field="*{email}" placeholder="Employee Email" class="form-control mb-4 col-4">

            <button type="submit" class="btn btn-info col-2"> Save Employee</button>
        </form>

        <hr>

        <a th:href="@{/}"> Back to Employee List</a>
    </div>
</body>

</html>

9.3 update_employee.html

Create update_employee.html file under "resources/templates" folder and add the following content to it:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="ISO-8859-1">
    <title>Employee Management System</title>

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
</head>

<body>
    <div class="container">
        <h1>Employee Management System</h1>
        <hr>
        <h2>Update Employee</h2>

        <form action="#" th:action="@{/saveEmployee}" th:object="${employee}" method="POST">

            <!-- Add hidden form field to handle update -->
            <input type="hidden" th:field="*{id}" />

            <input type="text" th:field="*{firstName}" class="form-control mb-4 col-4">

            <input type="text" th:field="*{lastName}" class="form-control mb-4 col-4">

            <input type="text" th:field="*{email}" class="form-control mb-4 col-4">

            <button type="submit" class="btn btn-info col-2"> Update Employee</button>
        </form>

        <hr>

        <a th:href="@{/}"> Back to Employee List</a>
    </div>
</body>

</html>

10. Run Spring application and demo


Run spring boot application with the following command:
$ mvn spring-boot:run

Demo

List Employees Page


Update Employee Page

Add Employee Page

GitHub Repository Link

You can download/clone source code of this tutorial from my GitHub repository at https://github.com/RameshMF/springboot-thymeleaf-crud-pagination-sorting-webapp.git

Further Readings

Pagination and Sorting with Spring Boot, ThymeLeaf, Spring Data JPA, Hibernate, MySQL

Comments