Spring Boot CRUD application example

This article expounds on the ERD design discussion by implementing a Spring Boot application for the design.We shall also expose REST APIs to interact with our application to perform the CRUD operations.

Creating a Sping Boot starter project

1.Head to https://start.spring.io/

2.Fill in project details as show in below screen shot. Choose selected dependencies as well i.e Spring Web,Spring Data JPA,MySQL Driver,Lombok

 

 

 

3.Click GENERATE below to download the starter project to a location of your choice.

NB:The starter projects downloads as a .zip

4.Extract the .zip package and open the project with your IDE.For this lesson,we will be using Netbeans.
5.Build the project so that dependencies can be downloaded.
6.The project now looks as below.

Let’s create some packages

  • Entities(package com.school.relationships.entities)-for storing database classes
  • Repositories(package com.school.relationships.repositories)-for the repository classes for interacting with the database
  • Services(package package com.school.relationships.services)-for services classes for exposing business logic/functionality
  • Controllers(package com.school.relationships.controllers)-for storage of controller classes for the project

Creating the entities

Entities are Java classes that model the database and provide an easy way of interacting with the database.However,writing entities manually is time consuming and error prone.We can pull/reverse engineer the entities from the database .This is opposite of forward engineering as we saw in Mysql Workbench when creating our database from the design.

Lets create a database connection

On Services tab,Right click Databases and choose New Connection.

Key in the database connection parameters and click test the connection with the ‘Test Connection’ option.

If Connection succeeds,click Next then Finish.

Activate the Projects tab and right click on the entities package,choose New and then Entity Classes from Database.

On the database connection option ,choose the connections we created  and click Next.

 

 

On the screen that appears click Next.On the last screen ,choose lazy for Association Fetch and java.util.list for Collection Type.

NB:lazy association fetches related tables/entities on demand and not when the initial object is loaded from query.This increases the performance of the query and the application.e.g when loading a student,the related subjects the student takes will not be loaded at that point since we might be loading a student to check other details that do not require the subject he/she takes.The opposite is true for eager fetch.lazy fetch is the default fetch type.

The below classes are added

 

Lets add the below database connection parameters on the application.properties file

spring.datasource.url=jdbc:mysql://localhost:3306/school

spring.datasource.username=root

spring.datasource.password=root

spring.jpa.hibernate.ddl-auto=none

Then build the project.

Crud repositories

Lets create the crud repositories for interacting with the database as shown below

@Repository

public interface AdmissionfileRepository extends CrudRepository<Admissionfile, Integer> {

}

@Repository

public interface DormitoryRepository extends CrudRepository<Dormitory, Integer>{   

}

@Repository

public interface StudentRepository extends CrudRepository<Student, Integer>{   

}

@Repository

public interface SubjectRepository extends CrudRepository<Subject, Integer>{   

}

@Repository

public interface TeacherRepository extends CrudRepository<Teacher, Integer>{   

}

 

Composite primary keys

Now,inorder to perform Crud operations on our join tables ‘student_has_subject’ and ‘teacher_has_subject’ we need to add some entities not generated by Netbeans during our reverse engineering step and  repositories for creating records of the entities in the database.

Composite primary keys are formed by combining the primary keys/unique keys of 2 or more tables in order to form a unique identifier.They are majorly used in Many to Many relationships.

-The below class models our composite primary keys for the student_has_subject table and teacher_has_subject tables .


package com.school.relationships.entities;

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Embeddable;

/**
 *
 * @author hp
 */
@Embeddable
public class StudentHasSubjectPK implements Serializable {

    @Basic(optional = false)
    @Column(name = "student_idstudent")
    private int studentID;

    @Basic(optional = false)
    @Column(name = "subject_idsubject")
    private int subjectID;

    public StudentHasSubjectPK() {
    }

    public StudentHasSubjectPK(int studentID, int subjectID) {
        this.studentID = studentID;
        this.subjectID = subjectID;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 37 * hash + this.studentID;
        hash = 37 * hash + this.subjectID;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final StudentHasSubjectPK other = (StudentHasSubjectPK) obj;
        if (this.studentID != other.studentID) {
            return false;
        }
        if (this.subjectID != other.subjectID) {
            return false;
        }
        return true;
    }

    /**
     * @return the studentID
     */
    public int getStudentID() {
        return studentID;
    }

    /**
     * @param studentID the studentID to set
     */
    public void setStudentID(int studentID) {
        this.studentID = studentID;
    }

    /**
     * @return the subjectID
     */
    public int getSubjectID() {
        return subjectID;
    }

    /**
     * @param subjectID the subjectID to set
     */
    public void setSubjectID(int subjectID) {
        this.subjectID = subjectID;
    }
}
 


package com.school.relationships.entities;

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Embeddable;

/**
 *
 * @author hp
 */
@Embeddable
public class TeacherHasSubjectPK implements Serializable {

    @Basic(optional = false)
    @Column(name = "teacher_idteacher")
    private int teacherID;

    @Basic(optional = false)
    @Column(name = "subject_idsubject")
    private int subjectID;

    public TeacherHasSubjectPK() {
    }

    public TeacherHasSubjectPK(int teacherID, int subjectID) {
        this.teacherID = teacherID;
        this.subjectID = subjectID;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 97 * hash + this.teacherID;
        hash = 97 * hash + this.subjectID;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final TeacherHasSubjectPK other = (TeacherHasSubjectPK) obj;
        if (this.teacherID != other.teacherID) {
            return false;
        }
        if (this.subjectID != other.subjectID) {
            return false;
        }
        return true;
    }

    /**
     * @return the teacherID
     */
    public int getTeacherID() {
        return teacherID;
    }

    /**
     * @param teacherID the teacherID to set
     */
    public void setTeacherID(int teacherID) {
        this.teacherID = teacherID;
    }

    /**
     * @return the subjectID
     */
    public int getSubjectID() {
        return subjectID;
    }

    /**
     * @param subjectID the subjectID to set
     */
    public void setSubjectID(int subjectID) {
        this.subjectID = subjectID;
    }

}
 

Lets also create below models represent our student_has_subject table and teacher_has_subject tables and which also use the above composite primary keys.

NB:We started with the composite primary keys first since they are needed as primary keys in the student_has_subject and teacher_has_subject tables.


package com.school.relationships.entities;

import java.io.Serializable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlRootElement;

/**
 *
 * @author hp
 */
@Entity
@Table(name = "student_has_subject")
@XmlRootElement
public class StudentHasSubject implements Serializable {

    private static final long serialVersionUID = 1L;
    @EmbeddedId
    private StudentHasSubjectPK studentHasSubjectPK;

    public StudentHasSubject() {
    }

    /**
     * @return the studentHasSubjectPK
     */
    public StudentHasSubjectPK getStudentHasSubjectPK() {
        return studentHasSubjectPK;
    }

    /**
     * @param studentHasSubjectPK the studentHasSubjectPK to set
     */
    public void setStudentHasSubjectPK(StudentHasSubjectPK studentHasSubjectPK) {
        this.studentHasSubjectPK = studentHasSubjectPK;
    }
}
 


package com.school.relationships.entities;

import java.io.Serializable;
import java.util.Objects;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlRootElement;

/**
 *
 * @author hp
 */
@Entity
@Table(name = "teacher_has_subject")
@XmlRootElement
public class TeacherHasSubject implements Serializable {

    private static final long serialVersionUID = 1L;
    @EmbeddedId
    private TeacherHasSubjectPK teacherHasSubjectPK;

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 89 * hash + Objects.hashCode(this.teacherHasSubjectPK);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final TeacherHasSubject other = (TeacherHasSubject) obj;
        if (!Objects.equals(this.teacherHasSubjectPK, other.teacherHasSubjectPK)) {
            return false;
        }
        return true;
    }

    /**
     * @return the teacherHasSubjectPK
     */
    public TeacherHasSubjectPK getTeacherHasSubjectPK() {
        return teacherHasSubjectPK;
    }

    /**
     * @param teacherHasSubjectPK the teacherHasSubjectPK to set
     */
    public void setTeacherHasSubjectPK(TeacherHasSubjectPK teacherHasSubjectPK) {
        this.teacherHasSubjectPK = teacherHasSubjectPK;
    }
}
 

Lets add below repositories for the extra 2 entities we created.

@Repository

public interface StudentHasSubjectRepository extends CrudRepository<StudentHasSubject, StudentHasSubjectPK> {

}

@Repository

public interface TeacherHasSubjectRepository extends CrudRepository<TeacherHasSubject, TeacherHasSubjectPK> {   

}

Next we proceed to create models to hold payload data of the entities we are inserting into the database as well as create service classes and controllers to expose our APIs.

The models to hold data

Request/response models are POJOs that hold payload data in API requests. They are lightweight in that they may not contain all the data of the underlying database entities they represent and also may be constructed in a way to leave out sensitive data from the underlying entities e.g. a StudentModel may omit the primary key of the underlying Student entity when serving API requests against a Student data processing system

-In our project,they are in the com.school.relationships.models package.

-Below is sample code for our StudentModel

 

package com.school.relationships.models;

import com.fasterxml.jackson.annotation.JsonFormat;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Date;

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;

import lombok.ToString;

 

/**

 *

 * @author hp

 */

@Data

@AllArgsConstructor

@NoArgsConstructor

@ToString

public class StudentModel {

    @JsonProperty("Surname")

    private String surname;

    @JsonProperty("Firstname")

    private String firstname;

    @JsonProperty("Lastname")

    private String lasttname;

    @JsonProperty("Dob")

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")

    private Date dateOfBirth;

    @JsonProperty("FileNumber")

    private Integer admissionFileNo;

    @JsonProperty("DormitoryNumber")

    private Integer dormitoryNo;

}

Implementing service classes

Service classes handle business logic of an application.In our case,we have several service classes namely AdmissionFileService, DormitoryService, StudentService, StudentSubjectService, SubjectService, TeacherService and TeacherSubjectService service.These classes are in the com.school.relationships.services packages.

Sample code for our StudentService is as below.

 

package com.school.relationships.services;

import com.school.relationships.entities.Admissionfile;

import com.school.relationships.entities.Student;

import com.school.relationships.models.StudentModel;

import com.school.relationships.repositories.StudentRepository;

import java.util.Optional;

import lombok.extern.slf4j.Slf4j;

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

import org.springframework.stereotype.Service;

 

/**

 *

 * @author hp

 */

@Service

@Slf4j

public class StudentService {

 

    @Autowired

    private StudentRepository studentRepository;

    @Autowired

    private DormitoryService dormitoryService;

    @Autowired

    private AdmissionFileService admissionFileService;

 

    public void createStudent(StudentModel model) {

        try {

            Student student = new Student();

            Admissionfile file = admissionFileService.findAdmissionFileByFileNumber(model.getAdmissionFileNo()).get();

            student.setFileNo(file);

            student.setDob(model.getDateOfBirth());

            student.setFirstName(model.getFirstname());

            student.setSurname(model.getSurname());

            student.setLastName(model.getLasttname());

            student.setDormitoryIddormitory(dormitoryService.findDormitoryById(model.getDormitoryNo()).get());

            studentRepository.save(student);

        } catch (Exception e) {

            log.error("Error creating student > {}", e);

            throw e;

        }

    }

 

    public Optional<Student> findStudentWithTheirSubjects(Integer idstudent) {

        return studentRepository.findStudentAndSubjectsTaken(idstudent);

    }

    public Optional<Student> findStudentById(Integer idstudent) {

        return studentRepository.findById(idstudent);

    }

}

 

Implementing controllers to expose REST API

We implement controller classes to expose REST endpoints which will process our HTTP requests for creating students, fetching students, linking students and subjects they take, creating subjects, creating teachers etc.

Our controllers are found in the com.school.relationships.controllers package.

Sample controller is show below,the StudentController class.It exposes HTTP methods for creating a student

  1. createStudent(/Student)-a POST method that takes in StudentModel and returns success on creating a student.
  2. subjectList(/Student/Subjects/{id})-a GET method that returns the subjects a student takes in.A path variable named id representing a student is passed get specific subjects for a specific student.
  3. findStudent (/Student/{id})-a GET method for getting specific student data.A path variable named id which is a student identifier is passed in.

 

 

package com.school.relationships.controllers;

import com.fasterxml.jackson.core.JsonProcessingException;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.school.relationships.entities.Student;

import com.school.relationships.models.StudentModel;

import com.school.relationships.services.StudentService;

import java.util.HashMap;

import java.util.Map;

import java.util.Optional;

import lombok.extern.slf4j.Slf4j;

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

import org.springframework.http.HttpStatus;

import org.springframework.http.MediaType;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

 

/**

 *

 * @author hp

 */

@RestController

@RequestMapping("/Student")

@Slf4j

public class StudentController {

 

    @Autowired

    private StudentService studentService;

   

   @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)

    public ResponseEntity<?> createStudent(@RequestBody StudentModel model) throws JsonProcessingException {

        Map<String, Object> responseMap = new HashMap<>();

        try {

            log.info("POST | Student Creation Operation = {}", model);

            responseMap.put("Status", "Success");

            studentService.createStudent(model);           

            return ResponseEntity.ok(new ObjectMapper().writeValueAsString(responseMap));

        } catch (Exception e) {

            responseMap.put("Status", "Fail");

            responseMap.put("Message", e.getMessage());

            return new ResponseEntity<>(new ObjectMapper().writeValueAsString(responseMap), HttpStatus.INTERNAL_SERVER_ERROR);

        }

    }   

    @GetMapping(value = "/Subjects/{id}", produces = MediaType.APPLICATION_JSON_VALUE)

    public ResponseEntity<?> subjectList(@PathVariable Integer id) throws JsonProcessingException {

        Map<String, Object> responseMap = new HashMap<>();

        try {

            log.info("GET |Student Subjects Operation");

            Optional<Student> result = studentService.findStudentWithTheirSubjects(id);

            responseMap.put("Status", "Success");

            responseMap.put("Student's Subjects", result.get().getSubjectList());

            return ResponseEntity.ok(new ObjectMapper().writeValueAsString(responseMap));

        } catch (Exception e) {

            responseMap.put("Status", "Fail");

            responseMap.put("Message", e.getMessage());

            return new ResponseEntity<>(new ObjectMapper().writeValueAsString(responseMap), HttpStatus.INTERNAL_SERVER_ERROR);

        }

    }   

    @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)

    public ResponseEntity<?> findStudent(@PathVariable Integer id) throws JsonProcessingException {

        Map<String, Object> responseMap = new HashMap<>();

        try {

            log.info("GET |Student Operation");

            Optional<Student> result = studentService.findStudentById(id);

            responseMap.put("Status", "Success");

            responseMap.put("Student", result.get());

            return ResponseEntity.ok(new ObjectMapper().writeValueAsString(responseMap));

        } catch (Exception e) {

            responseMap.put("Status", "Fail");

            responseMap.put("Message", e.getMessage());

            return new ResponseEntity<>(new ObjectMapper().writeValueAsString(responseMap), HttpStatus.INTERNAL_SERVER_ERROR);

        }

    }

}

The code in the above illustrations can be found at Github

About the Author - John Kyalo Mbindyo(Bsc Computer Science) is a Senior Application Developer currently working at NCBA Bank Group,Nairobi- Kenya.He is passionate about making programming tutorials and sharing his knowledge with other software engineers across the globe. You can learn more about him and follow him on  Github.