Composite Key with JPA and Hibernate
Composite Key with JPA and Hibernate
In this article let discuss on
How to map Composite Key with JPA and Hibernate
Domain Model
A relational database composite key contains two or more columns which together for the primary key of a given table.
In the diagram above, the employee
table has a Composite Key, which consists of two columns:
-
company_id
employee_number
Every Employee
can also have a Phone
, which uses the same composite key to reference its owning Employee
.
Composite Primary Key with JPA and Hibernate
To map this database table mapping, we need to isolate the compound key into an @Embeddable
first:
@Embeddable public class EmployeeId implements Serializable { @Column(name = "company_id") private Long companyId; @Column(name = "employee_number") private Long employeeNumber; public EmployeeId() { } public EmployeeId(Long companyId, Long employeeId) { this.companyId = companyId; this.employeeNumber = employeeId; } public Long getCompanyId() { return companyId; } public Long getEmployeeNumber() { return employeeNumber; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof EmployeeId)) return false; EmployeeId that = (EmployeeId) o; return Objects.equals(getCompanyId(), that.getCompanyId()) && Objects.equals(getEmployeeNumber(), that.getEmployeeNumber()); } @Override public int hashCode() { return Objects.hash(getCompanyId(), getEmployeeNumber()); } }
The JPA specification says that all entity identifiers should be serializable and implement
equals
andhashCode
.So, an
Embeddable
that is used as a composite identifier must beSerializable
and implementequals
andhashCode
.
The Employee
mapping looks as follows:
@Entity(name = "Employee") @Table(name = "employee") public class Employee { @EmbeddedId private EmployeeId id; private String name; public EmployeeId getId() { return id; } public void setId(EmployeeId id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
The
@EmbeddedId
is used to instruct Hibernate that theEmployee
entity uses a compound key.
The Phone
mapping is rather straightforward as well:
@Entity(name = "Phone") @Table(name = "phone") public class Phone { @Id @Column(name = "`number`") private String number; @ManyToOne @JoinColumns({ @JoinColumn( name = "company_id", referencedColumnName = "company_id"), @JoinColumn( name = "employee_number", referencedColumnName = "employee_number") }) private Employee employee; public Employee getEmployee() { return employee; } public void setEmployee(Employee employee) { this.employee = employee; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } }
The Phone
uses the number
as an entity identifier since every phone number and the @ManyToOne
mapping uses the two columns that are part of the compound key.
Mapping relationships using the Composite Key
We can even map relationships using the information provided within the Composite Key itself. In this particular example, the company_id
references a Company
entity which looks as follows:
@Entity(name = "Company") @Table(name = "company") public class Company { @Id private Long id; private String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Company)) return false; Company company = (Company) o; return Objects.equals(getName(), company.getName()); } @Override public int hashCode() { return Objects.hash(getName()); } }
We can have the Composite Key mapping referencing the Company
entity within the Employee
entity:
@Entity(name = "Employee") @Table(name = "employee") public class Employee { @EmbeddedId private EmployeeId id; private String name; @ManyToOne @JoinColumn(name = "company_id",insertable = false, updatable = false) private Company company; public EmployeeId getId() { return id; } public void setId(EmployeeId id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
Notice that the
@ManyToOne
association instructs Hibernate to ignore inserts and updates issued on this mapping since thecompany_id
is controlled by the@EmbeddedId
.
Mapping a relationships inside @Embeddable
But that’s not all. We can even move the @ManyToOne
inside the @Embeddable
itself:
@Embeddable public class EmployeeId implements Serializable { @ManyToOne @JoinColumn(name = "company_id") private Company company; @Column(name = "employee_number") private Long employeeNumber; public EmployeeId() { } public EmployeeId(Company company, Long employeeId) { this.company = company; this.employeeNumber = employeeId; } public Company getCompany() { return company; } public Long getEmployeeNumber() { return employeeNumber; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof EmployeeId)) return false; EmployeeId that = (EmployeeId) o; return Objects.equals(getCompany(), that.getCompany()) && Objects.equals(getEmployeeNumber(), that.getEmployeeNumber()); } @Override public int hashCode() { return Objects.hash(getCompany(), getEmployeeNumber()); } }
Now, the Employee
mapping will no longer require the extra @ManyToOne
association since it’s offered by the entity identifier:
@Entity(name = "Employee") @Table(name = "employee") public class Employee { @EmbeddedId private EmployeeId id; private String name; public EmployeeId getId() { return id; } public void setId(EmployeeId id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
The persistence logic changes as follows:
Company company = doInJPA(entityManager -> { Company _company = new Company(); _company.setId(1L); _company.setName("vladmihalcea.com"); entityManager.persist(_company); return _company; }); doInJPA(entityManager -> { Employee employee = new Employee(); employee.setId(new EmployeeId(company, 100L)); employee.setName("Vlad Mihalcea"); entityManager.persist(employee); }); doInJPA(entityManager -> { Employee employee = entityManager.find( Employee.class, new EmployeeId(company, 100L) ); Phone phone = new Phone(); phone.setEmployee(employee); phone.setNumber("012-345-6789"); entityManager.persist(phone); }); doInJPA(entityManager -> { Phone phone = entityManager.find(Phone.class, "012-345-6789"); assertNotNull(phone); assertEquals(new EmployeeId(company, 100L), phone.getEmployee().getId()); });
Conclusion
Knowing how to map a Composite Key with JPA and Hibernate is very important because this is the way you’d map a many-to-many association.
As demonstrated in this blog post, such a mapping is not complicated at all.

WeCanCode-Author
August 05, 2021
Java & C#.NET Developer with 10++ years of IT experience.
Planning to learn ReactJS or Angular or Flutter.!