Spring Boot CRUD Tutorial with Spring MVC, Spring Data JPA, Thymeleaf, Hibernate, MySQL

This tutorial will build a simple Student Management System web application using Spring Boot, Spring MVC, Thymeleaf, Spring Data JPA, and MySQL database.

We will build a CRUD operation for the Student entity in our Student Management System web application.

Tools and Technologies Used

1. Create Spring Boot Project

Let's open STS ( Spring Suite Tool) IDE to develop and bootstrap the Spring boot project.

Use the below guide to create a Spring boot project in Eclipse STS IDE:

=> Create Spring Boot Project in Spring Tool Suite [STS]

Selected below dependencies while creating spring boot project using spring initializr:

2. Create Spring Boot Project Structure

Let's create the below packages in our Spring boot project:

3. Maven Dependencies

Note that we are using Spring Boot 3 and it requires minimum Java version 17 or later.


<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>

4. Create Student JPA Entity

Let's create a Student JPA entity under the entity package and add the following content to it:


package net.javaguides.sms.entity;

import jakarta.persistence.*;

@Entity
@Table(name = "students")
public class Student {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(name = "first_name", nullable = false)
	private String firstName;
	
	@Column(name = "last_name")
	private String lastName;
	
	@Column(name = "email")
	private String email;
	
	public Student() {
		
	}
	
	public Student(String firstName, String lastName, String email) {
		super();
		this.firstName = firstName;
		this.lastName = lastName;
		this.email = 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;
	}
}

5. Create JPA StudentRepository

Let's create a StudentRepository interface under the repository package and add the following content:


package net.javaguides.sms.repository;

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

import net.javaguides.sms.entity.Student;

public interface StudentRepository extends JpaRepository<Student, Long>{

}

6. Configure MySQL Database

Before configuring the MySQL database configuration in our Spring boot project, first, create a database named SMS in MySQL workbench:

create database sms

Let's open the application.properties file and add following content to it:


spring.datasource.url=jdbc:mysql://localhost:3306/sms?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
spring.datasource.username=root
spring.datasource.password=root

#Hibernate
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect

#Hibernate auto ddl
spring.jpa.hibernate.ddl-auto=update

logging.level.org.hibernate.SQL=DEBUG

7. Creating Service Layer

StudentService Interface

Let's create a StudentService interface under the service package and add the following content to it:


package net.javaguides.sms.service;

import java.util.List;

import net.javaguides.sms.entity.Student;

public interface StudentService {
	List<Student> getAllStudents();
	
	Student saveStudent(Student student);
	
	Student getStudentById(Long id);
	
	Student updateStudent(Student student);
	
	void deleteStudentById(Long id);
}

StudentServiceImpl Class

Let's create a new package called impl inside the service package. Let's create StudentServiceImpl class and add the following content to it:


package net.javaguides.sms.service.impl;

import java.util.List;

import org.springframework.stereotype.Service;

import net.javaguides.sms.entity.Student;
import net.javaguides.sms.repository.StudentRepository;
import net.javaguides.sms.service.StudentService;

@Service
public class StudentServiceImpl implements StudentService{

	private StudentRepository studentRepository;
	
	public StudentServiceImpl(StudentRepository studentRepository) {
		super();
		this.studentRepository = studentRepository;
	}

	@Override
	public List getAllStudents() {
		return studentRepository.findAll();
	}

	@Override
	public Student saveStudent(Student student) {
		return studentRepository.save(student);
	}

	@Override
	public Student getStudentById(Long id) {
		return studentRepository.findById(id).get();
	}

	@Override
	public Student updateStudent(Student student) {
		return studentRepository.save(student);
	}

	@Override
	public void deleteStudentById(Long id) {
		studentRepository.deleteById(id);	
	}
}

8. Controller Layer

Let's create a StudentController class and add the following content to it:


    package net.javaguides.sms.controller;

    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.sms.entity.Student;
    import net.javaguides.sms.service.StudentService;
    
    @Controller
    public class StudentController {
        
        private StudentService studentService;
    
        public StudentController(StudentService studentService) {
            super();
            this.studentService = studentService;
        }
        
        // handler method to handle list students and return mode and view
        @GetMapping("/students")
        public String listStudents(Model model) {
            model.addAttribute("students", studentService.getAllStudents());
            return "students";
        }
        
        @GetMapping("/students/new")
        public String createStudentForm(Model model) {
            
            // create student object to hold student form data
            Student student = new Student();
            model.addAttribute("student", student);
            return "create_student";
            
        }
        
        @PostMapping("/students")
        public String saveStudent(@ModelAttribute("student") Student student) {
            studentService.saveStudent(student);
            return "redirect:/students";
        }
        
        @GetMapping("/students/edit/{id}")
        public String editStudentForm(@PathVariable Long id, Model model) {
            model.addAttribute("student", studentService.getStudentById(id));
            return "edit_student";
        }
    
        @PostMapping("/students/{id}")
        public String updateStudent(@PathVariable Long id,
                @ModelAttribute("student") Student student,
                Model model) {
            
            // get student from database by id
            Student existingStudent = studentService.getStudentById(id);
            existingStudent.setId(id);
            existingStudent.setFirstName(student.getFirstName());
            existingStudent.setLastName(student.getLastName());
            existingStudent.setEmail(student.getEmail());
            
            // save updated student object
            studentService.updateStudent(existingStudent);
            return "redirect:/students";		
        }
        
        // handler method to handle delete student request
        
        @GetMapping("/students/{id}")
        public String deleteStudent(@PathVariable Long id) {
            studentService.deleteStudentById(id);
            return "redirect:/students";
        }	
    }

9. View Layer

resources/templates/students.html

Let's create a students.html file and add the following content to it:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="ISO-8859-1">
<title>Student Management System</title>
<link rel="stylesheet"
	href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
	integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
	crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-md bg-dark navbar-dark">
  <!-- Brand -->
  <a class="navbar-brand" href="#">Student Management System</a>

  <!-- Toggler/collapsibe Button -->
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
    <span class="navbar-toggler-icon"></span>
  </button>

  <!-- Navbar links -->
  <div class="collapse navbar-collapse" id="collapsibleNavbar">
    <ul class="navbar-nav">
      <li class="nav-item">
        <a class="nav-link" th:href="@{/students}">Student Management</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" th:href="@{/students}">Teacher Management</a>
      </li>
    </ul>
  </div>
</nav>

	<div class ="container">
		<div class = "row">
			<h1> List Students </h1>
		</div>
		
		<div class = "row">
			<div class = "col-lg-3">
				<a th:href = "@{/students/new}" class = "btn btn-primary btn-sm mb-3"> Add Student</a>
			</div>
		</div>
		<table class = "table table-striped table-bordered">
			<thead class = "table-dark">
				<tr>
					<th> Student First Name</th>
					<th> Student Last Name</th>
					<th> Student Email </th>
					<th> Actions </th>
				</tr>
			</thead>
			
			<tbody>
				<tr th:each = "student: ${students}">
					<td th:text = "${student.firstName}"></td>
					<td th:text = "${student.lastName}"></td>
					<td th:text = "${student.email}"></td>
					<td>
						<a th:href = "@{/students/edit/{id}(id=${student.id})}"
						 class = "btn btn-primary">Update</a>
						
						<a th:href = "@{/students/{id}(id=${student.id})}"
						 class = "btn btn-danger">Delete</a>
						 
					</td>
				</tr>
			</tbody>
		
		</table>
		
	</div>
</body>
</html>

We have used th:each Thymeleaf attribute in our template to iterate the list of students:

resources/templates/create_student.html

Let's create a create_student.html file and add the following content to it:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="ISO-8859-1">
<title>Student Management System</title>
<link rel="stylesheet"
	href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
	integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
	crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-md bg-dark navbar-dark">
  <!-- Brand -->
  <a class="navbar-brand" href="#">Student Management System</a>

  <!-- Toggler/collapsibe Button -->
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
    <span class="navbar-toggler-icon"></span>
  </button>

  <!-- Navbar links -->
  <div class="collapse navbar-collapse" id="collapsibleNavbar">
    <ul class="navbar-nav">
      <li class="nav-item">
        <a class="nav-link" th:href="@{/students}">Student Management</a>
      </li>
    </ul>
  </div>
</nav>
<br>
<br>
	<div class = "container">
		<div class = "row">
			<div class ="col-lg-6 col-md-6 col-sm-6 container justify-content-center card">
				<h1 class = "text-center"> Create New Student </h1>
				<div class = "card-body">
					<form th:action="@{/students}" th:object = "${student}" method="POST">
						<div class ="form-group">
							<label> Student First Name </label>
							<input
							type = "text"
							name = "firstName"
							th:field = "*{firstName}"
							class = "form-control"
							placeholder="Enter Student First Name" 
							/>
						</div>
						
						<div class ="form-group">
							<label> Student Last Name </label>
							<input
							type = "text"
							name = "lastName"
							th:field = "*{lastName}"
							class = "form-control"
							placeholder="Enter Student Last Name" 
							/>
						</div>
						
						<div class ="form-group">
							<label> Student Email </label>
							<input
							type = "text"
							name = "email"
							th:field = "*{email}"
							class = "form-control"
							placeholder="Enter Student Email" 
							/>
						</div>
						
						<div class = "box-footer">
							<button type="submit" class = "btn btn-primary">
								Submit
							</button>
						</div>
					</form>
				
				</div>
			</div>
		</div>
	</div>
</body>
</html>

resources/templates/edit_student.html

Let's create an edit_student.html file and add the following content to it:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="ISO-8859-1">
<title>Student Management System</title>
<link rel="stylesheet"
	href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
	integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
	crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-md bg-dark navbar-dark">
  <!-- Brand -->
  <a class="navbar-brand" href="#">Student Management System</a>

  <!-- Toggler/collapsibe Button -->
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
    <span class="navbar-toggler-icon"></span>
  </button>

  <!-- Navbar links -->
  <div class="collapse navbar-collapse" id="collapsibleNavbar">
    <ul class="navbar-nav">
      <li class="nav-item">
        <a class="nav-link" th:href="@{/students}">Student Management</a>
      </li>
    </ul>
  </div>
</nav>
<br>
<br>
	<div class = "container">
		<div class = "row">
			<div class ="col-lg-6 col-md-6 col-sm-6 container justify-content-center card">
				<h1 class = "text-center"> Update Student </h1>
				<div class = "card-body">
					<form th:action="@{/students/{id} (id=${student.id})}" th:object = "${student}" method="POST">
						<div class ="form-group">
							<label> Student First Name </label>
							<input
							type = "text"
							name = "firstName"
							th:field = "*{firstName}"
							class = "form-control"
							placeholder="Enter Student First Name" 
							/>
						</div>
						
						<div class ="form-group">
							<label> Student Last Name </label>
							<input
							type = "text"
							name = "lastName"
							th:field = "*{lastName}"
							class = "form-control"
							placeholder="Enter Student Last Name" 
							/>
						</div>
						
						<div class ="form-group">
							<label> Student Email </label>
							<input
							type = "text"
							name = "email"
							th:field = "*{email}"
							class = "form-control"
							placeholder="Enter Student Email" 
							/>
						</div>
						
						<div class = "box-footer">
							<button type="submit" class = "btn btn-primary">
								Submit
							</button>
						</div>
					</form>
				
				</div>
			</div>
		</div>
	</div>
</body>
</html>

10. Run Spring Boot Application

Run the Spring boot application with the main class:


package net.javaguides.sms;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StudentManagementSystemApplication{

	public static void main(String[] args) {
		SpringApplication.run(StudentManagementSystemApplication.class, args);
	}
}

11. Demo

Once the Spring boot application is up and running then use the below URL to access this application:

URL: http://localhost:8080/students

List Students

Add Student

Update Student