Configuring Composite Primary key in JPA applications

Composite keys are a group of columns in the database, whose values together make a unique value. When using Hibernate or JPA applications there are two main strategies to map a Composite Primary Key.

Mapping a Composite key with an @IdClass

The name of the class is indicated as the value of the class attribute of the IdClass element, as
shown in this example:

@Entity
@IdClass(PersonId.class)
public class Person {

	@Id
	String name;
	@Id
	String surname;
	String address;
	String email;

    // Getter Setters and Constructors
}

As you can see, the Compound keys are annotated with @Id and we need to provide an @IdClass:

public class PersonId implements Serializable {
    String name;
    String surname;

   // Constructors
   // Getters /Setters equals and hashcode

    public PersonId(String name, String surname) {
        this.name = name;
        this.surname = surname;
    }
}

If using a Repository pattern to access your data, the compound key will be used as Type (instead of the Integer id field):

public interface PersonRepository extends CrudRepository<Person, PersonId> {

    List<Person> findBySurname(String surname);
}

Mapping a Composite key with an EmbeddedId

An embedded type is marked as such by adding the @Embeddable annotation to the class definition.
This annotation serves to distinguish the class from other regular Java types. Once a class has been
designated as embeddable, then its fields and properties will be persistable as part of an entity.

Here is a Class which uses an @Embeddable annotation:

@Entity
public class Customer {
	
    @EmbeddedId
    private CustomerEmbeddable customerPK;
 
    String address;
    String email;

   // Getter Setters and Constructors
   
}

As you can see, the compound key fields are not included in the Entity class. The CustomerEmbeddable class follows here:

@Embeddable
public class CustomerEmbeddable implements Serializable {
    String name;
    String surname;

   // Constructors
   // Getters /Setters  equals and hashcode

    public CustomerEmbeddable(String name, String surname) {
        this.name = name;
        this.surname = surname;
    }
}

You should as well adapt your Repository class to use the Embeddable primary key:

public interface CustomerRepository extends CrudRepository<Customer, CustomerEmbeddable> {

    List<Customer> findByEmail(String email);
}

So, which one should you choose? from the Database point of view, no changes are required when choosing one strategy or another

The @EmbeddedId strategy does not include the primary key fields in the Entity and thus communicates more clearly that the key is a composite key. Therefore, if you are using directly your Entity in your Controllers it makes more sense to use it. On the other hand, if using a DTO layer to hide the complexity of the Entity you can opt for the @IdClass which has the advantage to be less verbose when using HQL queries.

For example, compare:

select p.name from Person p

With:

select c.customerEmbeddable.name from Customer c

In conclusion, we have covered the two main strategies to map a compound primary key in a Hibernate / JPA application.

Source code for this example: https://github.com/fmarchioni/masterspringboot/tree/master/jpa/composite-pk