zgtangqian.com

Unlocking Hibernate 6: The Wonders of Custom Primary Keys

Written on

Introduction

In certain scenarios, you may need to create unique representations of primary keys while utilizing Hibernate. A common motivation for this is the need to form composite primary keys that consist of multiple attributes.

The simplest method to implement this in an entity class involves using several fields annotated with @Id. You will also need to create a separate class containing fields that correspond to the identifier attributes of the entity. Each of these ID classes must override the equals() and hashCode() methods.

The most straightforward and natural approach to implement this in Hibernate 6 is by utilizing Java Record and mapping it as either @IdClass or @EmbeddedId. This is because a record is efficient, easy to implement, immutable (similar to a primary key), and comes with built-in equals and hashCode methods.

Application Setup

Project Technical Stack

  1. Spring Boot 3.3.0
  2. Java 21

We will set up the following dependencies in the pom.xml file.

We will employ the Spring Data JPA dependency, and for our application’s database, we will use the H2 in-memory database.

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-jpa</artifactId>

</dependency>

<dependency>

<groupId>com.h2database</groupId>

<artifactId>h2</artifactId>

<scope>runtime</scope>

</dependency>

The Spring Data JPA dependency provides the necessary Hibernate dependencies for our project.

Mapping using @IdClass Identifier

Hibernate and the Jakarta Persistence specification necessitate that each primary key attribute of the entity class is represented by a singular primary key object. One way to achieve this is by providing an IdClass.

The @IdClass annotation designates a composite primary key type, with its fields or properties mapping to the attributes of the annotated entity class.

Starting from Hibernate ORM 6.5.0, it is possible to use a Record to create the IdClass.

Let’s define the entity class Student with primary key fields such as name, age, and address. These three attributes will be annotated with the @Id annotation, marking them as primary key attributes.

@Entity

@IdClass(StudentId.class)

public class Student {

@Id

private String name; // Id attribute Name with order-1

@Id

private Integer age; // Id attribute Age with order-2

@Id

private String address; // Id attribute Address with order-3

private String school;

}

You must reference the IdClass using the @IdClass annotation. The primary key fields in the entity should be annotated with @Id, and the Record class must have fields or properties that align with the entity’s names and types.

The @IdClass annotation on the Student entity specifies StudentId Record as the IdClass for that entity. The record StudentId must model the attribute name of type String, age of type Integer, and address of type String.

public record StudentId(String name, // Id attribute Name with order-1

Integer age, // Id attribute Age with order-2

String address) { // Id attribute Address with order-3

}

As demonstrated above, the attributes of the IdClass StudentId are defined in the same order as the @Id fields in the Student entity.

When executing the findById query with a StudentId record, the result may often be an empty Optional. Let’s delve into the reason for this.

Student student = new Student();

student.setName("RUCHIRA");

student.setAge(20);

student.setAddress("ADDRESS");

student.setSchool("SCHOOL");

studentRepository.save(student);

StudentId studentId = new StudentId("RUCHIRA", 20, "ADDRESS");

Optional<Student> studentOptional = studentRepository.findById(studentId);

studentOptional.ifPresentOrElse(student -> {

log.info("Student: {}", student);

}, () -> {

log.info("No Student Information");

});

Inconsistencies with @IdClass when Implemented as a Record

Hibernate internally employs an EmbeddableInstantiator to generate a record that represents the primary key value. This can impose significant limitations on how you design your IdClass record.

When Hibernate instantiates a new IdClass record, its default EmbeddableInstantiator assigns the values of the primary key attributes in alphabetical order based on their attribute names.

Consequently, if the attributes of the record are not organized in alphabetical order, the mapping between the record class attributes and the primary key attributes assigned by the EmbeddableInstantiator will become misaligned. This misalignment will lead to the findById method functioning incorrectly.

The proper way to define StudentId is to arrange the attributes in alphabetical order:

public record StudentId(String address,

Integer age,

String name) {

}

Let’s review the Hibernate logs after rearranging the attribute order:

StudentId studentId = new StudentId("ADDRESS", 20, "RUCHIRA");

Optional<Student> studentOptional = studentRepository.findById(studentId);

studentOptional.ifPresentOrElse(student -> {

log.info("Student: {}", student);

}, () -> {

log.info("No Student Information");

});

Mapping using @EmbeddedId Identifier

The @EmbeddedId annotation is utilized to define a composite primary key that is an Embeddable class or record. The embeddable class must be annotated as @Embeddable, and there should only be a single @EmbeddedId annotation with no @Id annotation.

A record marked with @Embeddable is created, incorporating all fields of the composite key. In this instance, the StudentId record contains fields for name, age, and address, and is designated as Embeddable.

@Embeddable

public record StudentId(String name, Integer age, String address) {

}

In the Student entity, instead of using a plain @Id, we will use @EmbeddedId to include the StudentId record. This composite key is linked with the corresponding fields in the Student entity.

@Entity

public class Student {

@EmbeddedId

private StudentId studentId;

private String school;

}

When executing the findById query with a StudentId record, the results remain consistent, regardless of the order of attributes in the Student record.

StudentId studentId = new StudentId("RUCHIRA", 20, "ADDRESS");

Student student = new Student();

student.setSchool("SCHOOL");

student.setStudentId(studentId);

studentRepository.save(student);

Optional<Student> studentOptional = studentRepository.findById(studentId);

studentOptional.ifPresentOrElse(student -> {

log.info("Student: {}", student);

}, () -> {

log.info("No Student Information");

});

It is evident that @EmbeddedId provides greater flexibility and does not have the same restrictions encountered with @IdClass when utilized with a record.

Conclusion

We can define our primary key representation using both @IdClass and @EmbeddableId mappings. This is essential when our primary key comprises multiple attributes.

According to the Jakarta Persistence specification following Hibernate ORM 6.5.0, we can implement an IdClass using a Record.

A record inherently includes equals and hashCode methods but lacks a no-argument constructor. Therefore, Hibernate utilizes an EmbeddableInstantiator to create an instance of the record for the primary key value. In doing so, Hibernate expects the fields of the record to be defined in alphabetical order.

Thank You for Reading

  • We welcome your feedback.
  • Stay tuned for more insightful content.

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

Strategic Brand Repositioning for Sustained Growth

Explore the concept of brand repositioning and its significance in adapting to market changes and driving growth.

The Surprising Truth: Nothing New in the Universe

Explore the idea that everything in the universe is just a rearrangement of existing matter, and why this is a fascinating realization.

Escape the Overtraining Trap: A Path to Recovery and Growth

Discover how to recognize and recover from overtraining while maintaining your fitness goals for a healthier lifestyle.

Encouraging Healthy Choices: Guiding Teens Through Alcohol and Tobacco

Strategies to help guide teenagers through the challenges of alcohol and tobacco experimentation while fostering open communication and trust.

Why Is the Minimum Wage Not Adjusted for Inflation?

An exploration of why minimum wage isn't indexed to inflation and its consequences.

Harnessing PowerShell for Wildfire Data Collection

Explore how to use PowerShell to gather real-time wildfire data, enhancing understanding and response efforts.

Revolutionizing Energy: Ultralight Solar Cells for Everyday Use

Explore how MIT's ultralight solar cells can transform surfaces into energy sources, enhancing portable and wearable tech.

Embrace Your Future Self for a Better Life Journey

Discover how connecting with your future self can transform your decision-making and enhance well-being.