An entity relationship describes how two tables are connected in the database, and JPA lets you show that connection as a link between two Java classes. A Student belongs to a Department, and a Department has many students. In the database this is a foreign key column (department_id) in the students table. In Java, you express it with annotations (@OneToMany, @ManyToOne), and JPA then loads and saves the related objects together. It is used so you can write student.getDepartment() or department.getStudents() instead of writing joins by hand.
Features
- The foreign key is created and filled in automatically.
- Cascade: saving or deleting a department can also save or delete its students.
- Related objects can be navigated in code, and searched through the link (
findByDepartmentId). - Orphan removal: a student taken out of a department’s list can be deleted automatically.
- One side owns the foreign key, and the other side only mirrors it (
mappedBy).
Entity Relationship Types
| Type | Meaning | Annotations | Example |
|---|---|---|---|
| 1. One-to-Many / Many-to-One | One row on one side links to many rows on the other. Used here | @OneToMany on Department, @ManyToOne on Student |
One department, many students |
| 2. One-to-One | One row links to exactly one row | @OneToOne |
One student, one ID card |
| 3. Many-to-Many | Many rows on both sides, stored through a third (join) table | @ManyToMany with @JoinTable |
Many students, many courses |
| Direction | Unidirectional: only one class knows the other. Bidirectional: both classes know each other. Used here | mappedBy marks the non-owning side |
Student knows Department, Department knows its students |
Hands-on Experiment: Creating a spring boot application to understand how associations between related database entities are represented using JPA.
The following steps are involved in developing this hands-on experiment.
Step 1: Generate the Project
Step 2: Project Structure
Step 3: pom.xml
Step 4: application.yml
Step 5: Create Entity Class
Step 6: Create Repository Interface
Step 7: Create Service Class
Step 8: Create Controller Class
Step 9: Main Application Class
Step 10: Run the Main Application Class
Step 1: Configure the Project on Spring Initializr
Go to start.spring.io and set:
- Project: Maven
- Language: Java
- Spring Boot version: 3.2.x
- Group: com.example
- Artifact: relationship
- Name: relationship
- Package name: com.example.relationship
- Packaging: Jar
- Java: 17
- Dependencies: Spring Web, Spring JPA, MySQL Driver.
Click Generate to download the ZIP, then extract and import it into your IDE as a Maven project.
Step 2: Generated Project Structure

Step 3: Explore pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>department-student-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
</properties>
<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>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Step 4: application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/student_relation_db?createDatabaseIfNotExist=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
server:
port: 8082
Step 5: Create Entity Class
package com.example.relationship.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "departments")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 100)
private String name;
// ONE department has MANY students.
// mappedBy = "department" -> the foreign key lives in the students table, owned by Student.department
// cascade = ALL -> saving or deleting a department also saves or deletes its students
// orphanRemoval = true -> a student removed from this list is deleted from the database
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnoreProperties("department") // stops the endless loop department -> students -> department ...
private List<Student> students = new ArrayList<>();
public Department() {}
public Department(String name) {
this.name = name;
}
// Keeps BOTH sides of the relationship in sync
public void addStudent(Student student) {
students.add(student);
student.setDepartment(this);
}
public void removeStudent(Student student) {
students.remove(student);
student.setDepartment(null);
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public List<Student> getStudents() { return students; }
// Used when a department is created from JSON that contains a "students" array
public void setStudents(List<Student> newStudents) {
this.students.clear();
if (newStudents != null) {
newStudents.forEach(this::addStudent);
}
}
}package com.example.relationship.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
@Entity
@Table(name = "students")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(nullable = false, unique = true, length = 150)
private String email;
// MANY students belong to ONE department.
// This is the OWNING side: it creates the foreign key column department_id in the students table.
@ManyToOne
@JoinColumn(name = "department_id")
@JsonIgnoreProperties("students") // show the department's id and name only, not its whole student list
private Department department;
public Student() {}
public Student(String name, String email) {
this.name = name;
this.email = email;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Department getDepartment() { return department; }
public void setDepartment(Department department) { this.department = department; }
}
Step 6: Create below Repository Interface
package com.example.relationship.repository;
import com.example.relationship.entity.Department;
import org.springframework.data.jpa.repository.JpaRepository;
public interface DepartmentRepository extends JpaRepository<Department, Long> {
boolean existsByNameIgnoreCase(String name);
}
package com.example.relationship.repository;
import com.example.relationship.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface StudentRepository extends JpaRepository<Student, Long> {
boolean existsByEmail(String email);
// Walks the relationship: student.department.id = ?
List<Student> findByDepartmentId(Long departmentId);
}
Step 7: Create below Service class
package com.example.relationship.service;
import com.example.relationship.entity.Department;
import com.example.relationship.repository.DepartmentRepository;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class DepartmentService {
private final DepartmentRepository repository;
public DepartmentService(DepartmentRepository repository) {
this.repository = repository;
}
// Saving a department also saves the students inside it (cascade)
public Optional<Department> create(Department department) {
if (repository.existsByNameIgnoreCase(department.getName())) {
return Optional.empty();
}
department.setId(null);
return Optional.of(repository.save(department));
}
public List<Department> findAll() {
return repository.findAll();
}
public Optional<Department> findById(Long id) {
return repository.findById(id);
}
public Optional<Department> rename(Long id, String newName) {
return repository.findById(id).map(department -> {
department.setName(newName);
return repository.save(department);
});
}
// Deleting a department also deletes its students (cascade)
public boolean delete(Long id) {
if (!repository.existsById(id)) {
return false;
}
repository.deleteById(id);
return true;
}
}
package com.example.relationship.service;
import com.example.relationship.entity.Department;
import com.example.relationship.entity.Student;
import com.example.relationship.repository.DepartmentRepository;
import com.example.relationship.repository.StudentRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Service
public class StudentService {
private final StudentRepository studentRepository;
private final DepartmentRepository departmentRepository;
public StudentService(StudentRepository studentRepository, DepartmentRepository departmentRepository) {
this.studentRepository = studentRepository;
this.departmentRepository = departmentRepository;
}
public boolean emailExists(String email) {
return studentRepository.existsByEmail(email);
}
// Adds a new student to an existing department. Empty result = department not found.
@Transactional
public Optional<Student> addToDepartment(Long departmentId, Student student) {
return departmentRepository.findById(departmentId).map(department -> {
student.setId(null);
student.setDepartment(department); // sets the foreign key department_id
return studentRepository.save(student);
});
}
public List<Student> findAll() {
return studentRepository.findAll();
}
public Optional<Student> findById(Long id) {
return studentRepository.findById(id);
}
public List<Student> findByDepartment(Long departmentId) {
return studentRepository.findByDepartmentId(departmentId);
}
// Moves a student to another department by changing the foreign key
@Transactional
public Optional<Student> moveToDepartment(Long studentId, Long departmentId) {
Optional<Student> student = studentRepository.findById(studentId);
Optional<Department> department = departmentRepository.findById(departmentId);
if (student.isEmpty() || department.isEmpty()) {
return Optional.empty();
}
student.get().setDepartment(department.get());
return student; // saved automatically when the transaction ends
}
public boolean delete(Long id) {
if (!studentRepository.existsById(id)) {
return false;
}
studentRepository.deleteById(id);
return true;
}
}
Step 8: Create Controller class
package com.example.relationship.controller;
import com.example.relationship.entity.Department;
import com.example.relationship.entity.Student;
import com.example.relationship.service.DepartmentService;
import com.example.relationship.service.StudentService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/departments")
public class DepartmentController {
private final DepartmentService departmentService;
private final StudentService studentService;
public DepartmentController(DepartmentService departmentService, StudentService studentService) {
this.departmentService = departmentService;
this.studentService = studentService;
}
// Body may contain a "students" array: they are saved together with the department
@PostMapping
public ResponseEntity<Object> create(@RequestBody Department department) {
return departmentService.create(department)
.<ResponseEntity<Object>>map(saved -> ResponseEntity.status(HttpStatus.CREATED).body(saved))
.orElseGet(() -> ResponseEntity.status(HttpStatus.CONFLICT)
.body(Map.of("error", "Department already exists")));
}
@GetMapping
public List<Department> getAll() {
return departmentService.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<Department> getById(@PathVariable Long id) {
return departmentService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PutMapping("/{id}")
public ResponseEntity<Department> rename(@PathVariable Long id, @RequestBody Department department) {
return departmentService.rename(id, department.getName())
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
// Also deletes the department's students
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
return departmentService.delete(id) ? ResponseEntity.noContent().build()
: ResponseEntity.notFound().build();
}
// ----- students inside a department -----
@GetMapping("/{id}/students")
public ResponseEntity<List<Student>> students(@PathVariable Long id) {
if (departmentService.findById(id).isEmpty()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(studentService.findByDepartment(id));
}
@PostMapping("/{id}/students")
public ResponseEntity<Object> addStudent(@PathVariable Long id, @RequestBody Student student) {
if (studentService.emailExists(student.getEmail())) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(Map.of("error", "Email already exists"));
}
return studentService.addToDepartment(id, student)
.<ResponseEntity<Object>>map(saved -> ResponseEntity.status(HttpStatus.CREATED).body(saved))
.orElseGet(() -> ResponseEntity.notFound().build());
}
}
package com.example.relationship.controller;
import com.example.relationship.entity.Student;
import com.example.relationship.service.StudentService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/students")
public class StudentController {
private final StudentService service;
public StudentController(StudentService service) {
this.service = service;
}
@GetMapping
public List<Student> getAll() {
return service.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<Student> getById(@PathVariable Long id) {
return service.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
// Move a student to another department
@PutMapping("/{id}/department/{departmentId}")
public ResponseEntity<Student> move(@PathVariable Long id, @PathVariable Long departmentId) {
return service.moveToDepartment(id, departmentId)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
return service.delete(id) ? ResponseEntity.noContent().build()
: ResponseEntity.notFound().build();
}
}
Step 9: Main Application
package com.example.relationship;
import com.example.relationship.entity.Department;
import com.example.relationship.entity.Student;
import com.example.relationship.repository.DepartmentRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.List;
@SpringBootApplication
public class RelationshipApplication {
public static void main(String[] args) {
SpringApplication.run(RelationshipApplication.class, args);
}
// Adds sample data once. Saving a Department also saves its students (cascade).
@Bean
CommandLineRunner seedData(DepartmentRepository departmentRepository) {
return args -> {
if (departmentRepository.count() == 0) {
Department cse = new Department("CSE");
cse.addStudent(new Student("Ravi Kumar", "ravi@example.com"));
cse.addStudent(new Student("Anil Verma", "anil@example.com"));
Department ece = new Department("ECE");
ece.addStudent(new Student("Sneha Reddy", "sneha@example.com"));
ece.addStudent(new Student("Ravi Teja", "teja@example.com"));
Department it = new Department("IT");
it.addStudent(new Student("Priya Nair", "priya@example.com"));
departmentRepository.saveAll(List.of(cse, ece, it));
}
};
}
}
Step 10: Run the RelationshipApplication


