Chapter 12.2☕ 28 min read

Project 2: Hyderabad Restaurant Menu API (JPA Relationships)

A real-world API rarely has isolated tables. This project builds a Menu API for a Hyderabadi Restaurant. A <code>Category</code> (like Biryani) has multiple <code>MenuItem</code>s (Chicken Biryani, Mutton Biryani). This ties together Phase 4 (JPA Relationships, Pagination) and Phase 3 (DTOs).

01The Concept: One-to-Many Relationships

The Paradise Takeaway Counter Analogy:

At Paradise, the menu isn't just a flat list of 500 items. It's organized. The "Biryani" section (Category) has multiple dishes under it. If you remove the Biryani section, the dishes under it might also disappear. This is a One-to-Many relationship.

02Technical Explanation & Tied Concepts
  1. @OneToMany & @ManyToOne: We map the relationship between Category and MenuItem.
  2. DTO Mapping: We fetch the Entity but return a DTO to the client to avoid infinite JSON recursion and hide database internals.
  3. Pagination: We allow the frontend to fetch menu items page by page (?page=0&size=5).
03Full Working Code

1. Entities & Repository (Menu.java)

package com.devinhyderabad;

import jakarta.persistence.*;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

@Entity
public class Category {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

@OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
private List<MenuItem> items;

public Long getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public List<MenuItem> getItems() { return items; }
public void setItems(List<MenuItem> items) { this.items = items; }
}

@Entity
class MenuItem {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;

@ManyToOne
@JoinColumn(name = "category_id")
private Category category;

public Long getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
public Category getCategory() { return category; }
public void setCategory(Category category) { this.category = category; }
}

interface MenuItemRepository extends JpaRepository<MenuItem, Long> {
Page<MenuItem> findAll(Pageable pageable);
}

2. Controller with Pagination & DTOs (MenuController.java)

package com.devinhyderabad;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/menu")
public class MenuController {
private final MenuItemRepository repo;
public MenuController(MenuItemRepository repo) { this.repo = repo; }

@GetMapping
public Page<MenuItemDTO> getMenu(@RequestParam int page, @RequestParam int size) {
return repo.findAll(PageRequest.of(page, size)).map(this::toDTO);
}

// Manual mapping to DTO to prevent infinite JSON recursion
private MenuItemDTO toDTO(MenuItem item) {
MenuItemDTO dto = new MenuItemDTO();
dto.setName(item.getName());
dto.setPrice(item.getPrice());
dto.setCategoryName(item.getCategory().getName());
return dto;
}
}

class MenuItemDTO {
private String name;
private double price;
private String categoryName;

public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
public String getCategoryName() { return categoryName; }
public void setCategoryName(String categoryName) { this.categoryName = categoryName; }
}
04Why It Matters

cascade = CascadeType.ALL par dhyan dein. In enterprise apps, this is risky. If you delete a Category, it deletes all MenuItems. Often, enterprises use soft deletes (is_deleted = true) instead of hard cascades to preserve audit trails and prevent accidental mass data loss.

05Interview Note

Interview Note: The DTO mapping here is done manually. In big projects, you would use MapStruct or ModelMapper. Interviewers ask: "Why DTOs?" Because returning the Entity directly exposes database internals (like category_id), and bidirectional JPA relationships (Category -> List<MenuItem> -> Category) cause infinite JSON loops. DTOs break the loop and give you control over the response shape.

Key Takeaways

  • ✅ @OneToMany and @ManyToOne map relationships between JPA entities (Category -> MenuItem)
  • ✅ DTOs prevent infinite JSON recursion and hide database internals from API consumers
  • ✅ Pagination with Pageable prevents sending thousands of records in one response
  • ✅ CascadeType.ALL is risky — enterprises prefer soft deletes over cascading hard deletes