How to map Composite Primary Key to Entity Class in JPA Hibernate

In this example I am going to tell you how to map composite primary key to entity class in your JPA Hibernate entity class. Composite primary key is a combination of two or more columns made together as a primary key. Therefore the combination of column values will be as a unique instead of having a single column value as unique.

Let’s say you have created a table called user in your database and the user table has three columns – id, name and email. So you don’t want to make the id column as a primary key but name and email together will be a unique and primary key. Here email and name fields make together a composite primary key.

In this example I am going to show you only Hibernate (JPA) standalone application. You can also use the same entity class in your Spring Boot application.

Prerequisites

Java at least 8, Hibernate 6.0.0.Alpha7, JPA 2.1

Project Setup

You can create a maven based project in your favorite IDE or tool. The name of the project is jpa-hibernate-composite-primary-key. The following pom.xml file can be used for your application. As you can see in the following build file I have added only Hibernate dependency. So JPA is a specification and Hibernate is one of the implementation frameworks for JPA (Java Persistence API).

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.roytuts</groupId>
	<artifactId>jpa-hibernate-composite-primary-key</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>12</maven.compiler.source>
		<maven.compiler.target>12</maven.compiler.target>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>6.0.0.Alpha7</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.22</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
			</plugin>
		</plugins>
	</build>

</project>

MySQL Table

Let’s say you have created the following user table under roytuts database in MySQL server.

CREATE TABLE user (
    id int unsigned COLLATE utf8mb4_unicode_ci NOT NULL,
    first_name varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL,
    last_name  varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL,    
    PRIMARY KEY (last_name,first_name)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Let’s say you want to make first_name and last_name as a composite primary key in the above user table. Now I will show you how to map the composite primary key in the entity class.

I am also inserting few rows to test the application right away:

INSERT INTO `user` (`id`, `first_name`, `last_name`) VALUES
	(3, 'Arup', 'Roy'),
	(1, 'Soumitra', 'Roy'),
	(2, 'Liton', 'Sarkar');

Composite Primary Key Class

To build a composite primary key class in Java using Hibernate (JPA), you need to have the no-argument constructor and you need to override equals() and hashCode() methods for building the logic to check equality.

The composite primary key class can be marked with annotation @Embeddable to use this class as a key in the entity class. Here you need to mention the table’s column names if they are different from Java field name using @Column annotation.

package com.roytuts.jpa.hibernate.composite.primary.key;

import java.io.Serializable;
import java.util.Objects;

import javax.persistence.Column;
import javax.persistence.Embeddable;

@Embeddable
public class UserPKey implements Serializable {

	private static final long serialVersionUID = 1L;

	@Column(name = "first_name")
	private String firstName;

	@Column(name = "last_name")
	private String lastName;

	public UserPKey() {
	}

	public UserPKey(String firstName, String lastName) {
		this.firstName = firstName;
		this.lastName = lastName;
	}

	public String getFirstName() {
		return firstName;
	}

	public String getLastName() {
		return lastName;
	}

	@Override
	public int hashCode() {
		return Objects.hash(getFirstName(), getLastName());
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		UserPKey other = (UserPKey) obj;

		return Objects.equals(getFirstName(), other.getFirstName())
				&& Objects.equals(getLastName(), other.getLastName());
	}

	@Override
	public String toString() {
		return "CompositePKey [firstName=" + firstName + ", lastName=" + lastName + "]";
	}

}

Entity Class

Now I will create the corresponding entity class for the user table.

Notice in the following entity class I have used @EmbeddedId on the composite primary key class to indicate it as a primary key.

package com.roytuts.jpa.hibernate.composite.primary.key;

import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Table;

@Entity
@Table(name = "user")
public class UserEntity {

	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	
	@EmbeddedId
	private UserPKey userPKey;

	public int getId() {
		return id;
	}

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

	@Override
	public String toString() {
		return "User [id=" + id + ", userPKey=" + userPKey + "]";
	}

	public UserPKey getUserPKey() {
		return userPKey;
	}

	public void setUserPKey(UserPKey userPKey) {
		this.userPKey = userPKey;
	}

}

Testing the Application

Let’s say you want to find a single user for the first and last name, so the query would be:

UserEntity user = em.find(UserEntity.class, compositePKey);

Where em is the instance of EntityManager class. The whole source code can be downloaded later from the Source Code section.

To get a single user you can use the following code snippets:

UserEntity userByPKey = userOp.getUser(new UserPKey("Soumitra", "Roy"));
System.out.println("By first and last name: " + userByPKey);

The above code snippets will give you the following output:

By first and last name: User [id=1, userPKey=CompositePKey [firstName=Soumitra, lastName=Roy]]

If you want to fetch all users from the table then your query can be written as follows:

String sql = "select u from UserEntity u";
List<UserEntity> users = em.createQuery(sql, UserEntity.class).getResultList();

To fetch all users from the table, use the code snippets:

List<UserEntity> detailsList = userOp.getUsers();

detailsList.stream().forEach(d -> System.out.println(d));

The above code snippets will give you the following output:

User [id=3, userPKey=CompositePKey [firstName=Arup, lastName=Roy]]
User [id=1, userPKey=CompositePKey [firstName=Soumitra, lastName=Roy]]
User [id=2, userPKey=CompositePKey [firstName=Liton, lastName=Sarkar]]

If you want to save or update then you can use the following JPA method:

em.persist(user);
em.getTransaction().commit();

Where user is the UserEntity entity class. If you want to update then make sure you pass the same first and last names otherwise a new entry will be created into the table because primary key is based on first and last names.

To add new user, use the following code snippets:

UserEntity addUser = new UserEntity();

UserPKey userPKey = new UserPKey("First", "Last");

addUser.setUserPKey(userPKey);

userOp.addUser(addUser);

The above code snippets will create a new entry in the table as given below:

map composite primary key to entity class in jpa hibernate

If you change the first name and last name as follows:

UserPKey userPKey=new UserPKey("Liton", "Sarkar");

Then you will get the following exception because a user with the same first and last names already exists in the table:

javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.roytuts.jpa.hibernate.composite.primary.key.UserEntity#CompositePKey [firstName=Liton, lastName=Sarkar]]

To remove you can pass the first and last names into the UserPKey class’s instance:

UserEntity ue = new UserEntity();
ue.setUserPKey(userPKey);

em.remove(ue);
em.getTransaction().commit();

To delete a user entity use the following code, I am assuming that a record with the following values exists in user table.

userOp.deleteUser(new UserPKey("Liton", "Sarkr"));

Now you will get the following exception:

java.lang.IllegalArgumentException: Removing a detached instance com.roytuts.jpa.hibernate.composite.primary.key.UserEntity#CompositePKey [firstName=Liton, lastName=Sarkr]
	at org.hibernate.event.internal.DefaultDeleteEventListener.disallowDeletionOfDetached

To resolve the above issue change the em.remove() method to the following:

em.remove(em.contains(ue) ? ue : em.merge(ue));

Now your record will be deleted and Hibernate will generate the following query for deleting the record:

delete 
    from
        user 
    where
        first_name=? 
        and last_name=?

Another Approach – Composite Primary Key

There can be another approach to map the composite primary key in entity class.

You need to change the UserPKey class as follows:

package com.roytuts.jpa.hibernate.composite.primary.key;

import java.io.Serializable;
import java.util.Objects;

import javax.persistence.Column;
import javax.persistence.Embeddable;

public class UserPKey implements Serializable {

	private static final long serialVersionUID = 1L;

	private String firstName;

	private String lastName;

	public UserPKey() {
	}

	public UserPKey(String firstName, String lastName) {
		this.firstName = firstName;
		this.lastName = lastName;
	}

	public String getFirstName() {
		return firstName;
	}

	public String getLastName() {
		return lastName;
	}

	@Override
	public int hashCode() {
		return Objects.hash(getFirstName(), getLastName());
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		UserPKey other = (UserPKey) obj;

		return Objects.equals(getFirstName(), other.getFirstName())
				&& Objects.equals(getLastName(), other.getLastName());
	}

}

The entity class can be written as follows:

package com.roytuts.jpa.hibernate.composite.primary.key;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;

@Entity
@IdClass(UserPKey.class)
@Table(name = "user")
public class UserEntity {

	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;

	@Id
	@Column(name = "first_name")
	private String firstName;

	@Id
	@Column(name = "last_name")
	private String lastName;

	public int getId() {
		return id;
	}

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

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@Override
	public String toString() {
		return "User [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + "]";
	}	

}

Notice in the above class I have made the individual field with @Id annotation to make the composite primary key. Also I have used @IdClass annotation on the class level to use the UserPKey class as a composite key.

Hope you got an idea how to make composite primary key and how to map composite primary key to the entity class.

You can download the source code from the below section.

Source Code

Download

Leave a Reply

Your email address will not be published. Required fields are marked *