Spring Boot + JPA: Querying Specific Columns of a Composite Primary Key (@EmbeddedId)

 

Spring Boot + JPA: Querying Specific Columns of a Composite Primary Key (@EmbeddedId)

Spring Boot + JPA: Querying Specific Columns of a Composite Primary Key (@EmbeddedId)

When working with Spring Boot and JPA, you may encounter situations where a composite primary key (Composite Key) is required. In particular, when using @EmbeddedId, you might need to query specific columns of the composite key. This post provides a detailed explanation of how to achieve this with practical examples and code.



1. Composite Primary Key and @EmbeddedId

A composite primary key consists of two or more columns that together uniquely identify a record in a table. In JPA, composite primary keys can be mapped using @EmbeddedId or @IdClass. This article focuses on the @EmbeddedId approach.

Defining the Composite Key Class

To represent a composite key, you must define a class annotated with @Embeddable. This class should implement the Serializable interface.

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

@Embeddable
public class CompositeKey implements Serializable {

    private String convsId;
    private String anotherId;

    // Default constructor
    public CompositeKey() {}

    // Constructor
    public CompositeKey(String convsId, String anotherId) {
        this.convsId = convsId;
        this.anotherId = anotherId;
    }

    // Getters and Setters
    public String getConvsId() {
        return convsId;
    }

    public void setConvsId(String convsId) {
        this.convsId = convsId;
    }

    public String getAnotherId() {
        return anotherId;
    }

    public void setAnotherId(String anotherId) {
        this.anotherId = anotherId;
    }

    // equals & hashCode methods
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CompositeKey that = (CompositeKey) o;
        return Objects.equals(convsId, that.convsId) && Objects.equals(anotherId, that.anotherId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(convsId, anotherId);
    }
}


Defining the Entity Class

The entity class that uses the composite key should reference the key class using the @EmbeddedId annotation.

import jakarta.persistence.*;

@Entity
public class MyEntity {

    @EmbeddedId
    private CompositeKey id;

    private String someData;

    // Default constructor
    public MyEntity() {}

    // Constructor
    public MyEntity(CompositeKey id, String someData) {
        this.id = id;
        this.someData = someData;
    }

    // Getters and Setters
    public CompositeKey getId() {
        return id;
    }

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

    public String getSomeData() {
        return someData;
    }

    public void setSomeData(String someData) {
        this.someData = someData;
    }
}


2. Querying Specific Columns of the Composite Key

When using @EmbeddedId, querying a specific column of the composite key can be achieved through the following methods.

Method 1: Using JPQL

In JPQL, you can access specific fields of the composite key using path navigation.

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import java.util.List;

public interface MyEntityRepository extends CrudRepository<MyEntity, CompositeKey> {

    @Query("SELECT e FROM MyEntity e WHERE e.id.convsId = :convsId")
    List<MyEntity> findByConvsId(String convsId);
}


Example Usage

@Autowired
private MyEntityRepository myEntityRepository;

public void testQuery() {
    String convsId = "12345";
    List<MyEntity> results = myEntityRepository.findByConvsId(convsId);
    results.forEach(System.out::println);
}


Method 2: Using Criteria API

The Criteria API provides a dynamic way to build queries programmatically.

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.*;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class MyEntityCriteriaRepository {

    @PersistenceContext
    private EntityManager entityManager;

    public List<MyEntity> findByConvsId(String convsId) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<MyEntity> query = cb.createQuery(MyEntity.class);
        Root<MyEntity> root = query.from(MyEntity.class);

        // Adding the condition on convsId
        Predicate condition = cb.equal(root.get("id").get("convsId"), convsId);
        query.where(condition);

        return entityManager.createQuery(query).getResultList();
    }
}


Example Usage

@Autowired
private MyEntityCriteriaRepository criteriaRepository;

public void testCriteria() {
    String convsId = "12345";
    List<MyEntity> results = criteriaRepository.findByConvsId(convsId);
    results.forEach(System.out::println);
}


Method 3: Using Example Matcher

Spring Data JPA’s Example matcher simplifies querying for specific conditions.

import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.repository.CrudRepository;

public interface MyEntityRepository extends CrudRepository<MyEntity, CompositeKey> {
    List<MyEntity> findAll(Example<MyEntity> example);
}

Example Usage

@Autowired
private MyEntityRepository myEntityRepository;

public void testExample() {
    CompositeKey key = new CompositeKey();
    key.setConvsId("12345");

    MyEntity probe = new MyEntity();
    probe.setId(key);

    ExampleMatcher matcher = ExampleMatcher.matching()
            .withMatcher("id.convsId", ExampleMatcher.GenericPropertyMatchers.exact());

    Example<MyEntity> example = Example.of(probe, matcher);
    List<MyEntity> results = myEntityRepository.findAll(example);
    results.forEach(System.out::println);
}


3. Choosing the Right Method

  • For simple queries: Use JPQL for clarity and simplicity.
  • For dynamic queries: The Criteria API is best suited for scenarios requiring multiple or flexible conditions.
  • For example-based queries: Use Example objects for straightforward use cases with sample data.


Summary

  • When working with @EmbeddedId, querying specific columns of a composite key is achievable through JPQL, Criteria API, or Example objects.
  • Choose the most suitable approach based on your query’s complexity and requirements.

I hope this guide helps you effectively manage composite keys in your JPA projects. Feel free to leave comments for further clarifications!

Comments