Spring Boot JPA Composite Key Example | @EmbeddedId vs @IdClass

In JPA, a composite key is a primary key that consists of multiple columns. There are two ways to define composite keys:

  1. Using @Embeddable and @EmbeddedId (Recommended)
  2. Using @IdClass (Alternative Approach)

We will cover both approaches with examples in a Spring Boot application using Spring Data JPA.



Approach 1: Using @Embeddable and @EmbeddedId (Recommended)

This is the preferred method because it allows better encapsulation of key fields.

Step 1: Create a Spring Boot Project

Use Spring Initializr to create a new project with the following dependencies:
✅ Spring Web
✅ Spring Data JPA
✅ H2 Database (for testing)


Step 2: Define the Composite Key Class

This class represents the primary key.

package com.example.demo.model;

import jakarta.persistence.Embeddable;
import java.io.Serializable;
import java.util.Objects;

@Embeddable
public class OrderKey implements Serializable {

    private Long orderId;
    private Long productId;

    public OrderKey() {}

    public OrderKey(Long orderId, Long productId) {
        this.orderId = orderId;
        this.productId = productId;
    }

    public Long getOrderId() {
        return orderId;
    }

    public void setOrderId(Long orderId) {
        this.orderId = orderId;
    }

    public Long getProductId() {
        return productId;
    }

    public void setProductId(Long productId) {
        this.productId = productId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        OrderKey orderKey = (OrderKey) o;
        return Objects.equals(orderId, orderKey.orderId) &&
               Objects.equals(productId, orderKey.productId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(orderId, productId);
    }
}

✔️ Key Points:

  • @Embeddable marks this class as an embeddable composite key.
  • Implements Serializable (mandatory for composite keys).
  • Overrides equals() and hashCode() to ensure proper key comparison.

Step 3: Create the Entity with @EmbeddedId

This entity class uses the composite key.

package com.example.demo.model;

import jakarta.persistence.*;

@Entity
@Table(name = "orders")
public class Order {

    @EmbeddedId
    private OrderKey id;

    private Integer quantity;
    private Double price;

    public Order() {}

    public Order(OrderKey id, Integer quantity, Double price) {
        this.id = id;
        this.quantity = quantity;
        this.price = price;
    }

    public OrderKey getId() {
        return id;
    }

    public void setId(OrderKey id) {
        this.id = id;
    }

    public Integer getQuantity() {
        return quantity;
    }

    public void setQuantity(Integer quantity) {
        this.quantity = quantity;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }
}

✔️ Key Points:

  • @EmbeddedId is used to specify the composite key.
  • The id field is an instance of OrderKey.

Step 4: Create the Repository Interface

package com.example.demo.repository;

import com.example.demo.model.Order;
import com.example.demo.model.OrderKey;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface OrderRepository extends JpaRepository<Order, OrderKey> {
}

✔️ Key Points:

  • JpaRepository<Order, OrderKey> → Uses OrderKey as the ID type.

Step 5: Create a Service Layer

package com.example.demo.service;

import com.example.demo.model.Order;
import com.example.demo.model.OrderKey;
import com.example.demo.repository.OrderRepository;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class OrderService {

    private final OrderRepository orderRepository;

    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public Order saveOrder(Order order) {
        return orderRepository.save(order);
    }

    public Optional<Order> getOrderById(OrderKey id) {
        return orderRepository.findById(id);
    }
}

✔️ Key Points:

  • Saves and retrieves orders using the composite key.

Step 6: Create a Controller

package com.example.demo.controller;

import com.example.demo.model.Order;
import com.example.demo.model.OrderKey;
import com.example.demo.service.OrderService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Optional;

@RestController
@RequestMapping("/orders")
public class OrderController {

    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @PostMapping
    public ResponseEntity<Order> createOrder(@RequestBody Order order) {
        return ResponseEntity.ok(orderService.saveOrder(order));
    }

    @GetMapping("/{orderId}/{productId}")
    public ResponseEntity<Order> getOrder(@PathVariable Long orderId, @PathVariable Long productId) {
        OrderKey id = new OrderKey(orderId, productId);
        Optional<Order> order = orderService.getOrderById(id);
        return order.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
    }
}

✔️ Key Points:

  • Exposes REST API for saving and retrieving orders using a composite key.

Step 7: Run and Test

Save an Order

POST /orders
{
  "id": {
    "orderId": 1,
    "productId": 101
  },
  "quantity": 2,
  "price": 499.99
}

Retrieve an Order

GET /orders/1/101

Approach 2: Using @IdClass (Alternative)

If you don’t want to use @EmbeddedId, you can use @IdClass.

Step 1: Define the Composite Key Class

package com.example.demo.model;

import java.io.Serializable;
import java.util.Objects;

public class OrderKey implements Serializable {
    private Long orderId;
    private Long productId;

    public OrderKey() {}

    public OrderKey(Long orderId, Long productId) {
        this.orderId = orderId;
        this.productId = productId;
    }

    // equals() and hashCode() should be implemented
}

Step 2: Modify the Entity

@Entity
@Table(name = "orders")
@IdClass(OrderKey.class)
public class Order {

    @Id
    private Long orderId;

    @Id
    private Long productId;

    private Integer quantity;
    private Double price;
}

✔️ Key Points:

  • @IdClass(OrderKey.class) maps multiple @Id fields to a primary key.

Conclusion

  • Use @EmbeddedId when you want a clean encapsulated key.
  • Use @IdClass when you prefer separate primary key fields inside the entity.

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete