CRUD REST Service With Spring Boot, Hibernate, and JPA

Hands on example of Writing a Spring Boot CRUD REST Service that provides GET, POST, PUT and DELTE endpoints with Spring Data JPA and Hibernate Integration.

Overview

A CRUD REST service allows HTTP GET, POST, PUT, and DELETE endpoints on the underlying resource. A client can use these endpoints to Create, Read, Update, and Delete resources by providing respective resource Identifier.

This tutorial explains a Step by Step way of building your own Spring Boot RESTfull CRUD Service to perform CRUD Operations on a Database resource by using Spring Data JPA, and Hibernate.

We will write a Students Service, which is an example of Spring Boot REST application. The service allows clients to add new students, find students, and modify or delete any existing students. In the backend we will use H2 database to store Students information.

If you are looking for creating a Spring Boot Application from scratch, please read How to Write Your Own Spring Boot REST Service. If you are new to Spring Data JPA, please read our JPA Guide Hands-on Spring Data JPA.

In this tutorial, we will cover:

  1. Required POM Dependencies
  2. Create Entity Class for Student
  3. Write Spring Data Repository
  4. Write a REST Controller having CRUD APIs
  5. Test

Dependencies

In order to run a basic Spring Boot JPA project we need web starter and data-jpa starter dependencies.

For Maven based projects add this to pom.xml

<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-web</artifactId>
  </dependency>
  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
  </dependency>
</dependencies>Code language: HTML, XML (xml)

Alternatively for a Gradle based project, add this to build.gradle file.

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'Code language: Gradle (gradle)

Also, we have added, H2 database dependency, which is a Java Based in-memory database. Spring Boot by default connect to an H2 database, if the database is available on class path. In other words, we do not need to provide any connection details for this database.

Write Entity Bean

We will create a Student class having @Entity annotation, to make it an entity bean. Also, the student class has basic fields, where the Id is autogenerated incremental field.

package com.amitph.spring.data.repo;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long student_id;
    private String firstName;
    private String lastName;
    private int year;
}Code language: Java (java)

We are using Lombok @Data annotations, which auto-generates all the getters, and setters for this class.

Create JPA Repository

Next, we will write a repository interface which extends from JpaRepository. Interestingly, we have not added any methods to this interface neither we are going to provide any implementation class for this. This is because, Spring Boot Data JPA auto implements this interface.

package com.amitph.spring.data.repo;

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

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
}Code language: Java (java)

Write Rest Controller

Next, we will write a Rest Controller for students and provide all the CRUD methods.

package com.amitph.spring.data.web;

import com.amitph.spring.data.repo.StudentRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequiredArgsConstructor
public class StudentController {
    private final StudentRepository studentRepository;
}Code language: Java (java)

The controller has a reference to StudentRepository which is a final field. The @RequiredArgumentsConstructor, adds a parameterised constructor for all the final fields. Thus, we do not need to explicitly auto-wire repository reference.

Now, we will add the CRUD API methods, one by one.

Write POST Method

A POST method is to create a new resource. Thus, we are using it to create new students, one at time.

@PostMapping("/students")
public void postStudent(@RequestBody StudentDto studentDto) {
    Student student = new Student();
    student.setFirstName(studentDto.getFirstName());
    student.setLastName(studentDto.getLastName());
    student.setYear(studentDto.getYear());
    studentRepository.save(student);
}Code language: Java (java)

User will send a post request, with the student details. Spring Boot @RequestBody annotation, maps the request body parameters into the StudentDto object. Next, we create a new instance of entity bean and set all the fields. However, we do not set id field, because it is auto-generated by hibernate. Finally, we ask repository to save the newly created entity bean.

Write PUT Method

User sends a PUT request to modify an existing resource. Hence, our API endpoint needs student Id in the request path.

@PutMapping("/students/{id}")
public void putStudent(@PathVariable long id, @RequestBody StudentDto studentDto) {
    Student student = new Student();
    student.setStudent_id(id);
    student.setFirstName(studentDto.getFirstName());
    student.setLastName(studentDto.getLastName());
    student.setYear(studentDto.getYear());
    studentRepository.save(student);
}Code language: Java (java)

We are mapping the request body into a StudentDto object. Using that, we are creating a new Student entity with the provided Id. Because of the same Id, the hibernate will not create a new record into the table. Instead, it will update the existing one.

Write DELETE Method

Writing a DELETE is very straightforward. We expect student Id to be present in the path variable and asking repository to delete the particular resource using Id.

@DeleteMapping("/students/{id}")
public void deleteStudent(@PathVariable long id) {
    studentRepository.deleteById(id);
}Code language: Java (java)

Write GET Method

Next is an example of a GET method where user can pass student Id as a path variable to get the Student. The endpoint throws StudentNotFoundException if a student with particular Id is not found.

@GetMapping("/students/{id}")
public Student getStudent(@PathVariable long id) {
    return studentRepository.findById(id).orElseThrow(StudentNotFoundException::new);
}Code language: Java (java)

A user may want to get a complete list of students instead. To do that, we will create another GET endpoint, which is generic and returns a List of Student objects.

@GetMapping("/students")
public List<Student> getStudents() {
    return studentRepository.findAll();
}Code language: Java (java)

Handle Not Found Exception

In the above endpoints, we throw a StudentNotFoundException. This class is an extension of RuntimeException, which returns HttpStatus.NOT_FOUND (404) in response.

package com.amitph.spring.data.web;

import org.springframework.web.bind.annotation.ResponseStatus;
import static org.springframework.http.HttpStatus.NOT_FOUND;

@ResponseStatus(NOT_FOUND)
public class StudentNotFoundException extends RuntimeException {
}Code language: Java (java)

If you are not aware of the ResponseStatus please read Spring Rest Service Exception Handling.

Run and Test

Let’s run the application and test all the endpoints. To do that, we are using curl, however you can also use Postman or any similar tool

Create New Student

~ curl --location --request POST 'localhost:8080/students' \
--header 'Content-Type: application/json' \
--data-raw '{
    "firstName" : "Strong",
    "lastName" : "Belwas",
    "year" :2025
}'Code language: Bash (bash)

Modify Student

In the next example, we are changing first name of the student, whose Id is 2.

~ curl --location --request PUT 'localhost:8080/students/2' \
--header 'Content-Type: application/json' \
--data-raw '{
    "firstName" : "JORY",
    "lastName" : "CASSEL",
    "year" : 2020
}'Code language: Bash (bash)

Retrieve Students

We will call a GET on students by passing the Id 2. The output on the very next line shows the respective Student is correctly returned.

~ curl --location --request GET 'localhost:8080/students/2'

{"student_id":2,"firstName":"JORY","lastName":"CASSEL","year":2020}%Code language: Bash (bash)

We can also GET all students by omitting the path variable of Id.

~ curl --location --request GET 'localhost:8080/students/'
[{"student_id":1,"firstName":"Strong","lastName":"Belwas","year":2025},{"student_id":2,"firstName":"JORY","lastName":"CASSEL","year":2020},{"student_id":3,"firstName":"Arthur","lastName":"Dayne","year":2022}]Code language: Bash (bash)

Delete Student

To delete a Student we will execute a DELETE request passing the Id as a path variable.

curl --location --request DELETE 'localhost:8080/students/2'Code language: Bash (bash)

Summary

In this hands on tutorial we learned How to Write a Spring Boot CRUD REST API Service using Hibernate and JPA. To do so, we wrote all the most essential components along with individual Http Request Handlers. Finally, we tested our API by executing POST, PUT, GET, and DELETE endpoints.

For full source code of the examples used here, please visit our Github Repository.