Spring JPA/Hibernate One-to-Many Association + PostgreSQL | SpringBoot CRUD RestAPIs Post/Get/Put/Delete example

spring-jpa-hibernate-one-to-many-association-postgresql-springboot-crud-restapis-post-get-put-delete-example-feature-image-new

In the tutorial, we show how to expose CRUD RestAPIs Post/Get/Put/Delete to interact with Hibernate Spring JPA One-to-Many association models using SpringBoot and PostgreSQL database.

Previous post:
Expose CRUD RestAPIs Spring JPA One-to-One Association

Related articles:
Spring JPA – Many to Many relationship
How to configure Spring JPA One to One Relationship – SpringBoot
Spring Data Rest – JPA One-to-Many relational entities | SpringBoot + MySql + HAL Browser
Kotlin SpringJPA Hibernate One-To-Many relationship

Related pages:

Technologies

  • Java 8
  • Maven 3.5.4
  • SpringToolSuite version 3.9.4.RELEASE
  • SpringBoot 2.0.4.RELEASE

Demo

Overview

We have 2 models Student & Assignment with One-to-Many association relationship:

jpa-hibernate-one-to-many-spring-boot-rest-apis-spring-jpa-one-to-many-postgresql-association-relationship

We create a SpringBoot project as below:

jpa-hibernate-one-to-many-spring-boot-rest-apis-spring-jpa-one-to-many-postgresql-architecture

jpa-hibernate-one-to-many-spring-boot-rest-apis-spring-jpa-one-to-many-postgresql-project-structure

Hibernate JPA configuration for 2 models Student & Assignment:

Student model ->


@Entity
@Table(name = "students")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 
public class Student implements Serializable{
	private static final long serialVersionUID = 1L;
	
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(name = "name")
	private String name;
	
	@Column(name = "age")
	private int age;
	
    @OneToMany(mappedBy = "student", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private Set assignments;
	
	public Student() {}
	
	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	// Getter & Setter methods
	// ...

Assignment model ->


@Entity
@Table(name = "assignments")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 
public class Assignment implements Serializable{
	private static final long serialVersionUID = 1L;
	
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(name = "name")
	private String name;
	
	@Column(name = "grade")
	private int grade;

	@ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "student_id", nullable = false)
    @JsonIgnore
    private Student student;
	
	public Assignment() {}
	
	public Assignment(String name, int grade) {
		this.name = name;
		this.grade = grade;
	}

	// Getter & Setter methods
	// ...

We exposes RestAPIs for Post/Get/Put/Delete Students & Assignments:

– Student ->

  • @GetMapping("/api/students"): get all Students
  • @GetMapping("/api/students/{id}"): get a Student by ID
  • @PostMapping("/api/students"): post a Student
  • @PutMapping("/api/students/{id}"): update a Student
  • @DeleteMapping("/api/students/{id}"): delete a Student

– Assignments ->

  • @GetMapping("/students/{studentId}/assignments"): get a Assignment by Student’s ID
  • @PostMapping("/students/{studentId}/assignments"): add an Assignment
  • @PutMapping("/students/{studentId}/assignments/{assignmentId}"): update an Assignment
  • @DeleteMapping("/students/{studentId}/assignments/{assignmentId}"): delete an Assignment by ID

Now we’ll create a project from scratch ->

Practice

Create SpringBoot project

We use SpringToolSuite to create a Java 8 SpringBoot project with below dependencies:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
 
<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<scope>runtime</scope>
</dependency>

OneToMany Models

Student model ->


package com.grokonez.springrestapi.onetomany.model;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@Entity
@Table(name = "students")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 
public class Student implements Serializable{
	private static final long serialVersionUID = 1L;
	
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(name = "name")
	private String name;
	
	@Column(name = "age")
	private int age;
	
    @OneToMany(mappedBy = "student", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private Set assignments;
	
	public Student() {}
	
	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public void setId(Long id) {
		this.id = id;
	}
	
	public Long getId() {
		return this.id;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getName() {
		return this.name;
	}
	
	public void setAge(int age) {
		this.age = age;
	}
	
	public int getAge() {
		return this.age;
	}
	
	public void setAssignments(Set assignments) {
		this.assignments = assignments;
	}
	
	public Set getAssignments(){
		return this.assignments;
	}
}

Assignment model ->


package com.grokonez.springrestapi.onetomany.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@Entity
@Table(name = "assignments")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 
public class Assignment implements Serializable{
	private static final long serialVersionUID = 1L;
	
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(name = "name")
	private String name;
	
	@Column(name = "grade")
	private int grade;

	@ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "student_id", nullable = false)
    @JsonIgnore
    private Student student;
	
	public Assignment() {}
	
	public Assignment(String name, int grade) {
		this.name = name;
		this.grade = grade;
	}
	
	public void setId(Long id) {
		this.id = id;
	}
	
	public Long getId() {
		return this.id;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getName() {
		return this.name;
	}
	
	public void setGrade(int grade) {
		this.grade = grade;
	}
	
	public int getGrade() {
		return this.grade;
	}
	
	public void setStudent(Student student) {
		this.student = student;
	}
	
	public Student getStudent() {
		return this.student;
	}
}

JPA Repositories

StudentRepository ->


package com.grokonez.springrestapi.onetomany.jpa;

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

import com.grokonez.springrestapi.onetomany.model.Student;

public interface StudentRepository extends JpaRepository {
}

AssignmentRepository ->


package com.grokonez.springrestapi.onetomany.jpa;

import java.util.List;

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

import com.grokonez.springrestapi.onetomany.model.Assignment;

public interface AssignmentRepository extends JpaRepository {
	List findByStudentId(Long studentId);	
}

Add datasource configurations in application.properties file ->


spring.datasource.url=jdbc:postgresql://localhost/testdb
spring.datasource.username=postgres
spring.datasource.password=123
spring.jpa.generate-ddl=true
#spring.jackson.serialization.fail-on-empty-beans=false

Expose Rest APIs

– Create NotFoundException ->


package com.grokonez.springrestapi.onetomany.exception;

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

@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "NotFoundException not found")
public class NotFoundException extends RuntimeException {
	private static final long serialVersionUID = 1L;
	
    public NotFoundException(String message) {
        super(message);
    }

    public NotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
}

StudentController APIs ->


package com.grokonez.springrestapi.onetomany.rest;

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

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.grokonez.springrestapi.onetomany.exception.NotFoundException;
import com.grokonez.springrestapi.onetomany.jpa.StudentRepository;
import com.grokonez.springrestapi.onetomany.model.Student;


@RestController
@RequestMapping("/api")
public class StudentController {
	
	@Autowired
	private StudentRepository studentRepository;
	
    @GetMapping("/students")
    public List getAllStudents() {
    	return studentRepository.findAll();
    }
    
    @GetMapping("/students/{id}")
    public Student getStudentByID(@PathVariable Long id) {
    	Optional optStudent = studentRepository.findById(id);
    	if(optStudent.isPresent()) {
    		return optStudent.get();
    	}else {
    		throw new NotFoundException("Student not found with id " + id);
    	}
    }
    
    @PostMapping("/students")
    public Student createStudent(@Valid @RequestBody Student student) {
        return studentRepository.save(student);
    }
    
    @PutMapping("/students/{id}")
    public Student updateStudent(@PathVariable Long id,
                                   @Valid @RequestBody Student studentUpdated) {
        return studentRepository.findById(id)
                .map(student -> {
                    student.setName(studentUpdated.getName());
                    student.setAge(studentUpdated.getAge());
                    return studentRepository.save(student);
                }).orElseThrow(() -> new NotFoundException("Student not found with id " + id));
    }
    
    @DeleteMapping("/students/{id}")
    public String deleteStudent(@PathVariable Long id) {
        return studentRepository.findById(id)
                .map(student -> {
                    studentRepository.delete(student);
                    return "Delete Successfully!";
                }).orElseThrow(() -> new NotFoundException("Student not found with id " + id));
    }
}

AssignmentController APIs ->


package com.grokonez.springrestapi.onetomany.rest;

import java.util.List;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.grokonez.springrestapi.onetomany.exception.NotFoundException;
import com.grokonez.springrestapi.onetomany.jpa.AssignmentRepository;
import com.grokonez.springrestapi.onetomany.jpa.StudentRepository;
import com.grokonez.springrestapi.onetomany.model.Assignment;

@RestController
@RequestMapping("/api")
public class AssignmentController {
	@Autowired
	private AssignmentRepository assignmentRepository;
	
	@Autowired
	private StudentRepository studentRepository;
	
    @GetMapping("/students/{studentId}/assignments")
    public List getContactByStudentId(@PathVariable Long studentId) {
    	
        if(!studentRepository.existsById(studentId)) {
            throw new NotFoundException("Student not found!");
        }
    	
    	return assignmentRepository.findByStudentId(studentId);
    }
    
    @PostMapping("/students/{studentId}/assignments")
    public Assignment addAssignment(@PathVariable Long studentId,
                            @Valid @RequestBody Assignment assignment) {
        return studentRepository.findById(studentId)
                .map(student -> {
                    assignment.setStudent(student);
                    return assignmentRepository.save(assignment);
                }).orElseThrow(() -> new NotFoundException("Student not found!"));
    }
    
    @PutMapping("/students/{studentId}/assignments/{assignmentId}")
    public Assignment updateAssignment(@PathVariable Long studentId,
    								@PathVariable Long assignmentId,
    								@Valid @RequestBody Assignment assignmentUpdated) {
    	
    	if(!studentRepository.existsById(studentId)) {
    		throw new NotFoundException("Student not found!");
    	}
    	
        return assignmentRepository.findById(assignmentId)
                .map(assignment -> {
                    assignment.setName(assignmentUpdated.getName());
                    assignment.setGrade(assignmentUpdated.getGrade());
                    return assignmentRepository.save(assignment);
                }).orElseThrow(() -> new NotFoundException("Assignment not found!"));
    }
    
    @DeleteMapping("/students/{studentId}/assignments/{assignmentId}")
    public String deleteAssignment(@PathVariable Long studentId,
    							   @PathVariable Long assignmentId) {
    	
    	if(!studentRepository.existsById(studentId)) {
    		throw new NotFoundException("Student not found!");
    	}
    	
        return assignmentRepository.findById(assignmentId)
                .map(assignment -> {
                    assignmentRepository.delete(assignment);
                    return "Deleted Successfully!";
                }).orElseThrow(() -> new NotFoundException("Contact not found!"));
    }
}

Run & Check Results

– Run the SpringBoot project with commandline mvn spring-boot:run.
2 tables is created in PostgreSQL ->

jpa-hibernate-one-to-many-spring-boot-rest-apis-spring-jpa-one-to-many-postgresql-initial-tables

– Add Students ->

jpa-hibernate-one-to-many-spring-boot-rest-apis-spring-jpa-one-to-many-postgresql-initial-tables

– Add Assignments ->

jpa-hibernate-one-to-many-spring-boot-rest-apis-spring-jpa-one-to-many-postgresql-post-a-assignment

– Get All Students ->

jpa-hibernate-one-to-many-spring-boot-rest-apis-spring-jpa-one-to-many-postgresql-get-all-students

– Get All Assignments of a Student->

jpa-hibernate-one-to-many-spring-boot-rest-apis-spring-jpa-one-to-many-postgresql-get-all-assignment-of-a-student

– Update Student ->

jpa-hibernate-one-to-many-spring-boot-rest-apis-spring-jpa-one-to-many-postgresql-put-a-student

– Update Assignment ->

jpa-hibernate-one-to-many-spring-boot-rest-apis-spring-jpa-one-to-many-postgresql-put-a-assignment

– Delete a Student -> image

jpa-hibernate-one-to-many-spring-boot-rest-apis-spring-jpa-one-to-many-postgresql-delete-a-student

– Delete an Assignment ->

jpa-hibernate-one-to-many-spring-boot-rest-apis-spring-jpa-one-to-many-postgresql-delete-an-assignment

Note: PostgreSQL commandline ->

PostgreSQL\9.6\bin>psql.exe --username="postgres" -W: connect to PostgreSQL ->


C:\Program Files\PostgreSQL\9.6\bin>psql.exe --username="postgres" -W
Password for user postgres:
psql (9.6.9)
WARNING: Console code page (437) differs from Windows code page (1252)
         8-bit characters might not work correctly. See psql reference
         page "Notes for Windows users" for details.
Type "help" for help.

\l: List of databases

\c testdb: connect to “testdb”

\d: List of relations ->


testdb=# \d
                 List of relations
 Schema |        Name        |   Type   |  Owner
--------+--------------------+----------+----------
 public | assignments        | table    | postgres
 public | assignments_id_seq | sequence | postgres
 public | students           | table    | postgres
 public | students_id_seq    | sequence | postgres
(4 rows)

\d assignments: Get Schema of a table assignments ->


testdb=# \d assignments;
                                   Table "public.assignments"
   Column   |          Type          |                        Modifiers
------------+------------------------+----------------------------------------------------------
 id         | bigint                 | not null default nextval('assignments_id_seq'::regclass)
 grade      | integer                |
 name       | character varying(255) |
 student_id | bigint                 | not null
Indexes:
    "assignments_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "fkn1mo9c6bajjx15gthydqdnv4b" FOREIGN KEY (student_id) REFERENCES students(id)

SourceCode

SpringBootRestAPIOneToMany



By grokonez | August 22, 2018.

Last updated on April 18, 2021.



Related Posts


4 thoughts on “Spring JPA/Hibernate One-to-Many Association + PostgreSQL | SpringBoot CRUD RestAPIs Post/Get/Put/Delete example”

  1. Nice article.

    I reproduce the code (not download your codes).

    I added the data. It was successfully.

    But, when I try to get the data via client it goes error :
    “`
    {
    “timestamp”: “2019-03-12T16:04:43.868+0000”,
    “status”: 500,
    “error”: “Internal Server Error”,
    “message”: “Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: com.hendisantika.springbootrestapionetomany.entity.Student[\”assignments\”])”,
    “path”: “/api/students/1”
    }
    “`

    Any suggets?

    Thanks

Got Something To Say:

Your email address will not be published. Required fields are marked *

*