avatarRuchira Madhushan Rajapaksha

Summary

This context provides a comprehensive guide on migrating to Hibernate 6, highlighting key changes, new features, and potential issues that may arise during the migration process.

Abstract

The article "Migrating to Hibernate 6: What’s new and what got changed?" discusses the most important steps to prepare for Hibernate 6 migration and the changes that come with it. It begins by explaining what Hibernate is and then delves into the new features of Hibernate 6, such as its use of Jakarta Persistence API 3.x, changes in default sequence naming, Instant and Duration mapping, and UUID generation approach. The article also highlights the easier mapping of JSON documents and the support for records as embeddables. It ends with a note of appreciation for the reader and an invitation to stay connected for more content.

Bullet points

  • Hibernate 6 uses Jakarta Persistence API 3.x (JPA 3), which means all configuration files and import statements need to be updated.
  • Upgrading to Java 11 is another migration that needs to be done, and it is advised to switch to Java 17.
  • Default sequence naming has changed in Hibernate 6, with each entity now having a specific sequence.
  • There are two options to solve the problem of entity-specific sequences: updating the database schema or adding a configuration parameter to tell Hibernate to use the old default sequences.
  • The mapping of Instant and Duration has changed in Hibernate 6, with Instant now mapped to SqlType.TIMESTAMP_UTC and Duration to SqlType.INTERVAL_SECOND.
  • The UUID generation approach has changed in Hibernate 6, with the UUID generator now needing to be defined explicitly.
  • Hibernate 6 simplifies the translation of JSON documents to Java objects and storing them in the database.
  • Hibernate 6 supports records as embeddables, but they cannot be used to model entities.
  • The article ends with a thank you note and an invitation to stay connected for more content.

Migrating to Hibernate 6: What’s new and what got changed?

All you need to know when Migrating to Hibernate 6

Migrating to Hibernate 6

Hello Guys 🫶

In this article, I will show you the most important steps to prepare your application for Hibernate 6.x migration and what you need to do when migrating your application. Also, we will discuss the new changes coming along with Hibernate 6 as well.

What is Hibernate?

Hibernate is a Java-based object-relational mapping (ORM) tool that provides a framework to map object-oriented domain models to relational databases and vice versa. Hibernate is concerned with data persistence as it applies to relational databases (via JDBC).

What’s new in Hibernate 6?

Hibernate 6.0 brought us many internal improvements. Let us explore these changes.

Hibernate 6 is Using Jakarta Persistence API 3.x (JPA 3)

Starting with Hibernate 6.0.0.Final, Hibernate has updated the dependency of the Jakarta Persistence API (JPA) specification has been updated to version 3. This means we need to use jakarta.persistence.* packages instead of old javax.persistence.* packages.

This means that all of your configuration files and import statements need to be updated, So this is a breaking change.

Upgrading to Java 11 is another migration that needs to be done, if it hasn’t already. We advise you to switch to Java 17, as that is the minimum JDK version required for Spring Boot 3.

But Spring Boot 3 comes in to picture with Hibernate 6

Because Spring Boot and Spring Data JPA work hand in hand and Spring Boot 3 by default uses Hibernate 6, migrating to Java 17 is inevitable.

Default Sequence Naming

The first thing you should check after migrating your persistence layer to Hibernate 6 is the generation of unique primary key values. If you use database sequences and each entity does not have a specified sequence, you must make some changes in the new Hibernate API.

Here, we have defined the generation strategy to Sequence without defining the sequence exactly, as no name is defined. In this case, Hibernate uses the default sequence named hibernate_sequence.

@Entity(name="Person")
public class Person {
 
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    private String name;
     
}

Hibernate has changed this approach. Now Hibernate uses a different sequence for every entity class if it is not defined explicitly. The entity name and the postfix _SEQ make up the name of that sequence, which is Person_SEQ.

This is a breaking change because the entity-specific sequences don’t exist in the database.

There are 2 options to solve this problem:

  1. Update your database schema to add the new sequences.
  2. Add a configuration parameter to tell Hibernate to use the old default sequences.

Out of these 2 approaches, the second approach is the quickest and easiest way to solve the problem, and you can still add the new sequences in a future release.

By setting the hibernate.id.db_structure_naming_strategy attribute in your persistence.xml file, you can instruct Hibernate to use the previous default sequences. You can configure Hibernate <5.3’s default sequences by setting this value to single and you can configure the Hibernate >=5.3 default sequence names by configuring the value legacy.

<persistence>
    <persistence-unit name="my-persistence-unit">
        ...
        <properties>
            <!-- ensure backward compatibility -->
            <property name="hibernate.id.db_structure_naming_strategy" value="legacy" />
 
            ...
        </properties>
    </persistence-unit>
</persistence>

If You are doing Hibernate 6 migration with Spring Boot, this can be configured with the property;

#Hibernate >=5.3
spring.jpa.properties.hibernate.id.db_structure_naming_strategy: legacy

#Hibernate < 50.3
spring.jpa.properties.hibernate.id.db_structure_naming_strategy: single

Changes in Instant and Duration Mapping

Another breaking change you will notice when migrating to Hibernate 6 is the mapping of Instant and Duration.

In Hibernate 5, Instant is mapped to SqlType.TIMESTAMP and Duration is mapped to Types.BIGINT. This mapping was changed in Hibernate 6. It now maps Instant to SqlType.TIMESTAMP_UTC and Duration to SqlType.INTERVAL_SECOND.

Since this is a breaking change, it will break the table mapping of existing applications. If you run into that problem, by setting the hibernate.type.preferred_duration_jdbc_type attribute to BIGINT and hibernate.type.preferred_instant_jdbc_type to TIMESTAMP you can resolve the issue.

<persistence>
    <persistence-unit name="my-persistence-unit">
        <properties>
            <!-- ensure backward compatibility -->
            <property name="hibernate.type.preferred_duration_jdbc_type" value="BIGINT" />
            <property name="hibernate.type.preferred_instant_jdbc_type" value="TIMESTAMP" />
 
            ...
        </properties>
    </persistence-unit>
</persistence>

Hibernate 6 introduced these two additional configuration parameters. But both of them are marked as Incubating meaning they might get changed or removed in the future.

The UUID Generation approach was changed

The UUID generator was implicit in Hibernate version 5. Hibernate automatically detected the UUID random generator after detecting the kind of ID field. But starting with Hibernate 6, we need to define the UUID primary key in the following way:

@Id
@UuidGenerator(style = UuidGenerator.Style.AUTO)
@GeneratedValue
private UUID id;

This way, we will define the generator and UUID generation style explicitly. UuidGenerator.Style is a value out of the following types:

AUTO: Defaults to RANDOM

de>RANDOM: Uses UUID.randomUUID() to generate values

de>TIME: Applies a time-based generation strategy consistent with IETF RFC 4122.

Easier Mapping of JSON Documents

Hibernate 6 simplifies translating JSON documents to Java objects and storing them in your database. To instruct Hibernate on how to read and write a JSON document to a database, as well as how to serialize and deserialize it, you had to define a custom UserType in Hibernate versions 4 and 5.

Hibernate can map a JSON document to an Embeddable as of version 6. An embeddable object is a Java class whose state maps to multiple columns of a table, but which doesn’t have its own persistent identity. It’s a class with mapped attributes but no @Id attribute. This way, it becomes part of the entity definition and gets mapped to the same database table.

You define an embeddable by implementing a Java class and annotating it with @Embeddable.

@Embeddable
public class Address implements Serializable {
   
    private String street;
       
    private String postalCode;
}

You can use the embeddable as an entity attribute once you’ve declared it. That required adding additional @Embedded annotation to the property. It instructs Hibernate to get additional mapping data from the embeddable class.

If you want to map the embeddable to a JSON document, you also need to annotate it with @JdbcTypeCode(SqlTypes.JSON) and include a JSON mapping library in your classpath.

@Entity(name="Person")
public class Person {
 
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    private String name;

    @Embedded 
    @JdbcTypeCode(SqlTypes.JSON)
    private Address address;
     
}

After that, Hibernate stores the attribute in the database by serializing and deserializing it to a JSON document. Your database dialect determines the type of column to which Hibernate maps the attribute.

Support for Records as Embeddables

Hibernate only supported records as DTO projections until Hibernate 6. But with the release of Hibernate 6, you can map Records as embeddables. But unfortunately, you can’t use records to model your entities.

For every record you wish to use as an embeddable in versions 6.0 and 6.1, you must implement an EmbeddableInstantiator. The EmbeddableInstantiator is a proprietary Hibernate interface that tells Hibernate how to instantiate an embeddable object. This allows you to model an embeddable as a record and gets rid of JPA’s requirement for a no-argument constructor.

Hibernate offers an EmbeddableInstantiator of its own for all embeddable records, starting with Hibernate version 6.2.

You define an embeddable record by annotating your record with an Embeddable annotation. You need to annotate it with EmbeddableInstantiator and reference your instantiator implementation if your hibernate version is 6.0 or 6.1.

@Embeddable
@EmbeddableInstantiator(PersonInitiator.class)
public record Person (String id, String name) {}
public class PersonInitiator implements EmbeddableInstantiator {
  
    public Object instantiate(ValueAccess valuesAccess, SessionFactoryImplementor sessionFactory) {
        final String id = valuesAccess.getValue(0, String.class);
        final String name = valuesAccess.getValue(1, String.class);
        
        return new Person( id, name );
    }
}

Thank You for Reading

  • Please feel free to share your feedback.
  • Stay connected for more insightful content.
Hibernate
Spring
Java
Spring Boot
Orm
Recommended from ReadMedium