Building a REST Service using Spring Boot and H2

Spring Boot is an open-source Java-based framework used to create a microservice.

It is used to build stand-alone and production-ready spring applications. Also, it offers benefits such as auto-configuration and dependency management.

H2 is an open-source lightweight Java database. It can be embedded in Java applications.

H2 is an in-memory database, that is, it never persists data on the disk. Instead, the data gets cleared once the database is refreshed. Thus, it is never used in production, mostly in development and test environments. Some of H2’s features are that it:

  • is an extremely fast database engine
  • has strong security features
  • supports standard SQL and JDBC API

Getting Started

In this tutorial, I would be building a simple rest service for a school. It involves creating a student record, assigning a student to a class, assigning a student to a course, getting the lists of all students record and getting all the students taking a course.

The technologies used include Maven, Java 1.8, Spring boot 2.3.2, IntelliJ, H2, and JPA.

Setting up the project

There are two ways to setup a spring boot application. Using:

For this tutorial, I would be using spring initialzr.

Spring Initialzr

This is a more convenient way to generate a spring boot project, all the dependencies needed can be easily setup without any tools. To begin:

  • Open Spring initialzr
  • Select the project type, in this case, it will be maven
  • Select the language; we are using Java
  • Select the project metadata (group, artifact, name, description, package name). The meaning of each metadata can be found in this reference guide.
  • Select dependencies. This project uses Spring Web, H2 Database and Spring Data JPA
  • Generate project.

Student Project Structure

Importing the Project

After downloading the project, the next step is to extract the zip folder and import into the IDE. The pom.xml file contains the information about the project and configuration details (the ones selected above) used by Maven to run the project.

pom.xml file

Project Structure

The project is split into six sections: Model, DTO, Repository, Service, Controller and Error.

1. Model

The model contains the data application. It contains the different entity classes that represent the database tables.

Defining the Entities

The first entity is the Student entity which represents the student data, it has four attributes to keep it simple. It also has a many-to-one relationship with two entity classes; the student class entity and the course entity.

The @Entity annotation specifies that the class is an entity and is mapped to a database table. The @Table annotation specifies the table name.

@Entity
@Table(name="student")
public class  Student {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;

    private String firstName;

    private String lastName;

    private String age;

    @JsonIgnore
    @ManyToOne
    @JoinColumn(name = "studentclassid")
    private StudentClass studentClass;

    @JsonIgnore
    @ManyToOne
    @JoinColumn(name = "course")
    private Course course;
}

The second entity is the Student Class entity which represents the Student Class data. It has four attributes and a One-to-Many relationship with the Student Entity.

@Entity
@Table(name="student_class")
public class StudentClass {

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

private String className;

private String noOfStudents;

private String classLevel;


@OneToMany(mappedBy="studentClass")
@JsonManagedReference
private List<Student> students;
}

The Third Entity is the Course Entity. It has two attributes and a One-to-Many relationship with the Student Entity

@Entity
@Table(name="course")
public class Course {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;

private String courseName;



@OneToMany(mappedBy="course")
@JsonManagedReference
private List<Student> students;
}

2. DTO

The Data Transfer Object (DTO) is like a data structure. It is an object that carries data between processes, the means of data transport between systems.

The DTO’s are split into Request and Response DTO’s.

StudentRequestDTO
public class StudentRequestDTO {
private String firstName;

private String lastName;

private String age;
}
StudentResponseDTO
public class StudentResponseDTO {

private Long id;

private String firstName;

private String lastName;

private String age;

private Long studentClassId;

private String studentClassName;

private Long courseId;

private String courseName;
}

CourseRequestDTO

The DTO for creating a new course

public class CourseRequestDTO {

private String courseName;
}
StudentClassRequestDTO

The DTO for creating a student class

public class StudentClassRequestDTO {

private String className;

private String noOfStudents;

private String classLevel;
}
AddStudentCourseDTO

The DTO for assigning a Student to a Course

public class AddStudentCourseDTO {

private Long courseId;
}

AddStudentClassDTO

The DTO for assign a Student to a Class

public class AddStudentClassDTO {

private Long studentClassId;
}

3. Repository

In Spring, a repository is a mechanism for encapsulating storage, retrieval and search behaviour which emulates a collection of objects.

Student Repository
import com.education.education.model.Student;

import org.springframework.data.repository.CrudRepository;

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

public interface StudentRepository extends CrudRepository<Student, Long> {

Optional<Student> findById(Long studentId);

List<Student> findBycourse(Long studentCourseId);
}
Course Repository
public interface CourseRepository extends CrudRepository<Course, Long> {

Optional<Course> findById(Long courseId);
}
Student Class Repository
public interface StudentClassRepository extends CrudRepository<StudentClass, Long> {

Optional<StudentClass> findById(Long studentClassId);
}

4. Service

The Service implementation is where all of the implementation is done and interacts with the the repository. The Service exposes methods that will be called from the controller. The @Service annotation is used to mark the class as a service provider.

Student Service Implementation
@Service
public class StudentServiceImpl implements StudentService {

@Autowired
private StudentRepository studentRepository;

@Autowired
private StudentClassRepository studentClassRepository;

@Autowired
private CourseRepository courseRepository;
@Override
public Student createStudent(StudentRequestDTO studentRequestDTO) {
Student student = new Student();
student.setAge(studentRequestDTO.getAge());
student.setFirstName(studentRequestDTO.getFirstName());
student.setLastName(studentRequestDTO.getLastName());



return studentRepository.save(student);

}

@Override
public Student addCourse(AddStudentCourseDTO addStudentCourseDTO, Long studentId)
{
Optional<Student> student = studentRepository.findById(studentId);
Student student1 = student.get();
if(student1 ==null){
throw new ResourceNotFoundException("No student record found");
}
if(addStudentCourseDTO.getCourseId() !=null){
Optional<Course> course = courseRepository.findById
(addStudentCourseDTO.getCourseId());
Course course1 = course.get();
student1.setCourse(course1);
}
return studentRepository.save(student1);

}

@Override
public Student addStudentClass(AddStudentClassDTO addStudentClassDTO,
Long studentId) {
Optional<Student> student = studentRepository.findById(studentId);
Student student1 = student.get();
if(student1 ==null){
throw new ResourceNotFoundException("No student record found");
}
if(addStudentClassDTO.getStudentClassId() !=null){
Optional<StudentClass> studentClass = studentClassRepository.findById
(addStudentClassDTO.getStudentClassId());
StudentClass studentClass1 = studentClass.get();
student1.setStudentClass(studentClass1);
}
return studentRepository.save(student1);

}

@Override
public ResponseEntity<List<StudentResponseDTO>> fetchStudents(Pageable pageable)
{
List<StudentResponseDTO> studentList = new ArrayList<>();
studentRepository.findAll().forEach(student -> {

Course course = student.getCourse();
Long courseId = course.getId();
String courseName = course.getCourseName();
StudentClass studentClass = student.getStudentClass();
Long studentClassId = studentClass.getId();
String studentClassname = studentClass.getClassName();

studentList.add(new StudentResponseDTO(student.getId(),
student.getFirstName(), student.getLastName(), student.getAge(),
studentClassId, studentClassname, courseId, courseName));
});

return new ResponseEntity<>(studentList, HttpStatus.OK);
}
@Override
public ResponseEntity<List<StudentResponseDTO>> fetchStudentsByCourse
(Pageable pageable, Long studentCourseId) {
List<StudentResponseDTO> studentList = new ArrayList<>();
studentRepository.findBycourse(studentCourseId).forEach(student -> {


Course course = student.getCourse();
Long courseId = course.getId();
String courseName = course.getCourseName();
StudentClass studentClass = student.getStudentClass();
Long studentClassId = studentClass.getId();
String studentClassname = studentClass.getClassName();

studentList.add(new StudentResponseDTO(student.getId(),
student.getFirstName(), student.getLastName(), student.getAge(),
studentClassId, studentClassname, courseId, courseName));
});

return new ResponseEntity<>(studentList, HttpStatus.OK);
}
Student Service
public interface StudentService {

Student createStudent(StudentRequestDTO studentRequestDTO);

Student addCourse(AddStudentCourseDTO addStudentCourseDTO, Long studentId);

Student addStudentClass(AddStudentClassDTO addStudentClassDTO, Long studentId);

ResponseEntity<List<StudentResponseDTO>> fetchStudents(Pageable pageable);

ResponseEntity<List<StudentResponseDTO>> fetchStudentsByCourse
(Pageable pageable, Long studentCourseId);


}
Course Service Implementation
@Service
public class CourseServiceImpl implements CourseService {

@Autowired
private CourseRepository courseRepository;

@Autowired
private StudentRepository studentRepository;
@Override
public Course createCourse(CourseRequestDTO courseRequestDTO) {
Course course = new Course();
course.setCourseName(courseRequestDTO.getCourseName());
course.setStudents(null);
return courseRepository.save(course);

}





}
Course Service
public interface CourseService {

Course createCourse(CourseRequestDTO courseRequestDTO);


}
Student Class Service Implementation
@Service
public class StudentClassServiceImpl implements StudentClassService {

@Autowired
private StudentRepository studentRepository;

@Autowired
private StudentClassRepository studentClassRepository;

@Override
public StudentClass createStudentClass(StudentClassRequestDTO studentClassRequestDTO)
{
StudentClass studentClass = new StudentClass();
studentClass.setClassLevel(studentClassRequestDTO.getClassLevel());
studentClass.setClassName(studentClassRequestDTO.getClassName());
studentClass.setNoOfStudents(studentClassRequestDTO.getNoOfStudents());


return studentClassRepository.save(studentClass);

}

}
Student Class Service
public interface StudentClassService {

StudentClass createStudentClass(StudentClassRequestDTO studentClassRequestDTO);

}

5. Controllers

Different methods are defined in the controller, these methods will be called by different endpoints. The @RestController annotation is a convenience annotation for creating restful controllers.

Student Controller
@RestController
@RequestMapping("/v1")
public class StudentController {

@Autowired
private StudentService studentService;

@RequestMapping(path = "/createStudent", method = RequestMethod.POST)
public Student createStudent(@RequestBody StudentRequestDTO studentRequestDTO) {
Student createStudent = studentService.createStudent(studentRequestDTO);
return createStudent;
}
@RequestMapping(path = "/addStudentCourse/{studentid}",
method = RequestMethod.PUT)
public Student addCourse(@RequestBody AddStudentCourseDTO addStudentCourseDTO,
@PathVariable Long studentid ) {
Student addCourse = studentService.addCourse(addStudentCourseDTO, studentid);
return addCourse;
}

@RequestMapping(path = "/addStudentClass/{studentid}"
, method = RequestMethod.PUT)
public Student addClass(@RequestBody AddStudentClassDTO addStudentClassDTO,
@PathVariable Long studentid ) {
Student addClass = studentService.addStudentClass(addStudentClassDTO,
studentid);
return addClass;
}


@RequestMapping(value = "/students", method = RequestMethod.GET)
public ResponseEntity<List<StudentResponseDTO>>getStudents
(@PageableDefault(value = 20) Pageable pageable) {
return studentService.fetchStudents(pageable);
}
@RequestMapping(value = "/students/{studentcourseid}", method = RequestMethod.GET)
public ResponseEntity<List<StudentResponseDTO>>getStudentsByCourse
(@PathVariable(value = "studentcourseid") Long studentCourseId,
@PageableDefault(value = 20) Pageable pageable) {
return studentService.fetchStudentsByCourse(pageable,studentCourseId);
}

}
Student Class Controller
@RestController
@RequestMapping("/v1")
public class StudentClassController {
@Autowired
private StudentClassService studentClassService;

@RequestMapping(path = "/createStudentClass", method = RequestMethod.POST)
public StudentClass createStudentClass
(@RequestBody StudentClassRequestDTO studentClassRequestDTO) {
StudentClass createStudentClass = studentClassService.
createStudentClass(studentClassRequestDTO);
return createStudentClass;
}
}
Course Controller
@RestController
@RequestMapping("/v1")
public class CourseController {

@Autowired
private CourseService courseService;

@RequestMapping(path = "/createCourse", method = RequestMethod.POST)
public Course createCourse(@RequestBody CourseRequestDTO courseRequestDTO) {
Course createCourse = courseService.createCourse(courseRequestDTO);
return createCourse;
}

}

6. Error

Create a ResourceNotFound exception to be thrown when a resource cannot be found.

public class ResourceNotFoundException extends RuntimeException{

public ResourceNotFoundException(){

}

public ResourceNotFoundException(String msg){
super(msg);
}
}

Testing the Endpoint

Postman will be used to test and run the endpoints defined above. The project will be using the default localhost port; 8080.

Lets start the application by running the main class EducationApplication.java

The expected output looks like this;

application

Test Cases

Case 1

Create New Course Object

Steps:

  • Open Postman
  • Set Request Type as POST
  • Set Url = http://localhost:8080/v1/createCourse
  • Set Request Header = Content-Type:application/json
  • Set the Body as {“courseName”: “Software Engineering”}
  • Click Send
  • In the response, we will get the id, course name and the student list displayed as null
Create a new course

To validate that the data got inserted into the H2 Database, first set up the H2 Database in the application.properties.

H2 Database Properties

# To Enable H2 Console
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
# For in memory db
#spring.datasource.url=jdbc:h2:mem:testdb
# H2 driver
spring.datasource.driverClassName=org.h2.Driver
# Default user name
spring.datasource.username=sa
# Default password
spring.datasource.password=
# JPA Platform
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

After setting the properties and restarting the application, click on the url; http://localhost:8080/h2-console/login.jsp?jsessionid=02859894ca79ad783c164c9ea0468638 and set the fields as required

h2 database

Connect to the database and select all from the Course Table

course table

Case 2

Create a new Student Object

Steps;

  • Open Postman
  • Set Request Type as POST
  • Set Url = http://localhost:8080/v1/createStudent
  • Set Request Header = Content-Type:application/json
  • Set the Body as {“firstName”: “kate”, “lastName”: “henshaw”, “age”: “20” }
  • Click Send
  • In the response, we will get the id, first name, last name and the age
Create New Student

To validate that the data got inserted into the H2 Database , run the “select * from student” query.

Student table

Case 3

Create a new Student Class Object

Steps;

  • Open Postman
  • Set Request Type as POST
  • Set Url = http://localhost:8080/v1/createStudentClass
  • Set Request Header = Content-Type:application/json
  • Set the Body as {“className”: “alpha”, “noOfStudents”: “10”, “classLevel”: “1001” }
  • Click Send
  • In the response, we will get the id, class name, no of students, class level and the list of students set to null

To validate that the data got inserted into the H2 Database , run the “select * from student_class” query.

Student Class Table

Case 4

Assigning a Student to a Course

Steps;

  • Open Postman
  • Set Request Type as PUT
  • Set Url = http://localhost:8080/v1/addStudentCourse/{studentId} which in this case is 1
  • Set Request Header = Content-Type:application/json
  • Set the Body as {“courseId”: “1” }
  • Click Send
  • In the response, we get the id, first name, last name, age. The course assigned doesn’t show in the response because the @JsonIgnore annotation was added to the course object
Add Student Course

Case 5

Assigning a Student to a Class

Steps;

  • Open Postman
  • Set Request Type as PUT
  • Set Url = http://localhost:8080/v1/addStudentClass/{studentId} which in this case is 1
  • Set Request Header = Content-Type:application/json
  • Set the Body as {“studentClassId”: “1” }
  • Click Send
  • In the response, we get the id, first name, last name, age. The Student Class assigned doesn’t show in the response because the @JsonIgnore annotation was added to the Student Class object
Add Student Class

Case 6

Retrieving the Student list

Steps;

  • Open Postman
  • Set Request Type as GET
  • Set Url = http://localhost:8080/v1/students
  • Set Request Header = Content-Type:application/json
  • Click Send
  • In the response, we will two student data because two student objects were created

Case 7

Retrieving the List of students taking a particular course

Steps;

  • Open Postman
  • Set Request Type as GET
  • Set Url = http://localhost:8080/v1/students/{courseid} which is 1
  • Set Request Header = Content-Type:application/json
  • Click Send
  • In the response, we will two student data because two student objects were created
List of students taking a particular course

Conclusion

In this tutorial, we’ve covered building a rest service using Spring Boot with the H2 database. The demo student application covers the POST, PUT and GET Methods.

Feel free to play around with the code and experiment! Reach out to me on Twitter if you have any questions

Tags:
, ,
1 Comment
  • Adesope Yusuf
    Posted at 18:37h, 07 August Reply

    You killed it

Post A Comment