Entity Relationships

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

Scroll to Top