Mapping a composite key with JPA e Hibernate

Mapping a Composite Key With JPA and Hibernate

In this article, we will see how to map a composite key with JPA and Hibernate using the annotations that JPA makes available to us.

Composite Key

A composite key is a combination of two or more columns that together form a primary key for a table.

JPA offers us two possibilities to define the mapping of a composite key, through the @IdClass and @EmbeddedId annotations.

In both cases, in order to map a composite key we must implement the Serializable interface and define the equals and hashCode methods.

@IdClass Mapping

In this example we will use a table called task which has two columns, task_type and task_number which together form a composite key.

Mapping a composite key with JPA e Hibernate

Now let’s proceed with the mapping by creating a TaskId class with the following properties.

public class TaskId implements Serializable {

    @Column(name = "task_number")
    private String taskNumber;
    
    @Column(name = "task_type")
    private String taskType;

    public TaskId() {
    }

    public TaskId(String taskNumber, String taskType) {
        this.taskNumber = taskNumber;
        this.taskType = taskType;
    }

    // getters, equals and hashCode
    
}

Now it’s time to associate the TaskId class with the Task class.

To do this we need to insert the @IdClass annotation in the Task class, we also need to declare the properties of the TaskId class inside the Task class and annotate them with @Id.

@Entity
@Table(name = "task")
@IdClass(TaskId.class)
public class Task {

    @Id
    private String taskNumber;
    
    @Id
    private String taskType;
    
    // getters and setters

}

@EmbeddedId Mapping

The @EmbeddedId annotation is another possibility to map a composite key.

This time we’ll use a table called employee which has two columns, employee_id and department_id which together form a composite key.

Mapping a composite key with JPA e Hibernate

Now, let’s proceed with the mapping of the EmployeeId class.

@Embeddable
public class EmployeeId implements Serializable {

    @Column(name = "employee_id")
    private Long employeeId;

    @Column(name = "department_id")
    private Long departmentId;

    public EmployeeId() {
    }

    public EmployeeId(Long employeeId, Long departmentId) {
        this.employeeId = employeeId;
        this.departmentId = departmentId;
    }
    
    // getters, equals and hashCode

}

After that, we need to embed this class into Employee class using @EmbeddedId.

@Entity
@Table(name = "employee")
public class Employee {
    
    @EmbeddedId
    private EmployeeId employeeId;
    
    // constructors, getters and setters

}

Mapping Relationships Using Composite Key

We can also map relationships using the composite key.

This time we will make use of two tables, one called language with a column code as a primary key and another table called translation which has two columns, translation_id and language_code which together form a composite key however language_code is also a foreign key.

Let’s proceed with the mapping by creating the Language class.

@Entity
@Table(name = "language")
public class Language {
    
    @Id
    private String code;
    
    // getters and setters

}

Now to map a composite key with relationship we have two options, we can map it inside the Translation entity or inside the TranslationId class with the @Embeddable annotation.

Relationship Inside Entity

Let’s create the TranslationId class.

@Embeddable
public class TranslationId implements Serializable {

    @Column(name = "translation_id")
    private Long translationId;

    @Column(name = "language_code")
    private String languageCode;

    public TranslationId() {
    }

    public TranslationId(Long translationId, String languageCode) {
        this.translationId = translationId;
        this.languageCode = languageCode;
    }
    
    // getters, equals and hashCode

}

Now let’s create the Translation class with relationship mapping.

@Entity
@Table(name = "translation")
public class Translation {

    @EmbeddedId
    private TranslationId translationId;

    @ManyToOne
    @JoinColumn(name = "language_code", insertable = false, updatable = false)
    Language language;
    
    // getters and setters

}

We specified in the @ManyToOne relationship to ignore inserts and updates issued on this mapping since language_code is controlled by @EmbeddedId.

Relationship inside @Embeddable

This time the TranslationId class will be mapped with the @ManyToOne annotation.

@Embeddable
public class TranslationId implements Serializable {

    @Column(name = "translation_id")
    private Long translationId;

    @ManyToOne
    @JoinColumn(name = "language_code")
    private Language language;

    public TranslationId() {
    }

    public TranslationId(Long translationId, Language language) {
        this.translationId = translationId;
        this.language = language;
    }
    
    // getters, equals and hashCode

}

Our Translation entity will no longer include the relationship.

@Entity
@Table(name = "translation")
public class Translation {

    @EmbeddedId
    private TranslationId translationId;
    
    // getters and setters

}

Using @IdClass or @EmbeddedId

The main difference between the @IdClass and @EmbeddedId annotations is that with @IdClass, you need to specify the primary key columns twice: once in the composite primary key class and then again in the entity class with the @Id annotation.

The @EmbeddedId annotation is more detailed than @IdClass because you can access the entire object marked as primary key using the field access method.

Another difference between @IdClass and @EmbeddedId is in the construction of the JPQL query.

For example with @IdClass the construction of the query is a little easier:

SELECT task.taskNumber FROM Task task

While with @EmbeddedId there is a need to write more, for example:

SELECT employee.employeeId.departmentId FROM Employee employee

If we access parts of the composite key individually, we can use @IdClass, but for scenarios where we frequently use the composite key as an object, @EmbeddedId is the better choice.

Conclusion

In this article we have seen how it is possible to map a composite key with JPA and Hibernate, through two different ways. We also saw how a relationship can be mapped to a composite key, and the differences between @IdClass and @EmbeddedId.
The project is available on GitHub.

Lorenzo Miscoli

Software Developer specialized in creating and designing web applications. I have always loved technology and dreamed of working in the IT world, to make full use of my creativity and realize my ideas.
Scroll to Top