JPA - Composite Primary Keys

Composite Primary Keys

A composite key is a primary key that is made up of more than one column to uniquely identify records in a table. Unlike a single-column primary key, a composite key combines two or more columns to ensure uniqueness. While any of the individual columns in a composite key might not be unique on their own, together they form a unique combination that can uniquely identify each row in the table

source: https://www.geeksforgeeks.org/composite-key-in-sql/

A composite primary key, also called a composite key, is a combination of two or more columns to form a primary key for a table.

In JPA, we have two options to define the composite keys:

  1. @IdClass annotation
  2. @EmbeddedId annotation

If we have a table called Account and it has two columns, accountNumber and accountType, that form the composite key.

The IdClass Annotation

public class AccountId implements Serializable {
    private String accountNumber;

    private String accountType;

    // default constructor

    public AccountId(String accountNumber, String accountType) {
        this.accountNumber = accountNumber;
        this.accountType = accountType;
    }

    // equals() and hashCode()
}

Using AccountId in the entity. In order to do that, we need to annotate the entity with the @IdClass annotation. We must also declare the fields from the AccountId class in the entity Account and annotate them with @Id

@Entity
@IdClass(AccountId.class)
public class Account {
    @Id
    private String accountNumber;

    @Id
    private String accountType;

    // other fields, getters and setters
}

The EmbeddedId Annotation

@Embeddable
public class BookId implements Serializable {
    private String title;
    private String language;

    // default constructor

    public BookId(String title, String language) {
        this.title = title;
        this.language = language;
    }

    // getters, equals() and hashCode() methods
}

Then we need to embed this class in the Book entity using @EmbeddedId

@Entity
public class Book {
    @EmbeddedId
    private BookId bookId;

    // constructors, other fields, getters and setters
}

JPA Repository and Method Naming

@Repository
public interface BookRepository extends JpaRepository<Book, BookId> {

    List<Book> findByIdName(String name);

    List<Book> findByIdAuthor(String author);
}

We use a part of the id variable’s field names to derive our Spring Data query methods. Hence, JPA interprets the partial primary key query as:

findByIdName -> directive "findBy" field "id.name"
findByIdAuthor -> directive "findBy" field "id.author"

Major differeces

  1. With @IdClass, we have to specify the column names twice. Once in the Composite class definition. The second time in the entity definition. With @EmbeddedId, we only specify the column names once.
  2. For example, these different structures affect the JPQL queries that we write.
    1. With @IdClass, the query is a bit simpler:
      SELECT account.accountNumber FROM Account account
      
    2. With @EmbeddedId, we have to do one extra traversal:
      SELECT book.bookId.title FROM Book book
      

References

  1. https://www.baeldung.com/jpa-composite-primary-keys
  2. https://www.baeldung.com/spring-jpa-embedded-method-parameters