Skip to content

Migration Guide 3.0: Hibernate ORM 5 to 6 migration

Yoann Rodière edited this page Sep 11, 2024 · 45 revisions

What is this?

This guide is intended as a quick reference about the most common issues when migration from Hibernate ORM 5.6 to 6.2 in the context of Quarkus (Quarkus 2 to 3).

It is not intended as a complete reference, but should be updated regularly based on issues most reported by the community.

For a complete reference, refer to the official Hibernate ORM migration guides:

For changes related to Quarkus specifically, and not to the Hibernate ORM 5.6 → 6.2 migration, see the main migration guide.

API

Packages and hints: javax.persistence. becomes jakarta.persistence.

Affected applications

Any application using JPA directly. The vast majority of Hibernate ORM applications use JPA directly to some extent.

Such application would import JPA packages (import javax.persistence.<something>;) or use JPA query hints (query.setHint("javax.persistence.<something>", …​))

Breaking changes

Hibernate ORM 6 implements Jakarta Persistence instead of Java EE Persistence API.

See here for more information.

Symptoms
  • Compilation failure.

  • Ignored query hints.

Actions
  • Replace all references to packages javax.persistence. with jakarta.persistence.

  • Replace all references to query hints starting with javax.persistence. with jakarta.persistence.. Alternatively (and preferably), use constants from org.hibernate.jpa.AvailableHints instead of hardcoding the hint names.

Legacy Hibernate Criteria API

Affected applications

Any application importing legacy Hibernate Criteria API types.

Such applications would necessarily call Session#createCriteria at some point.

Breaking changes

The deprecated, legacy Hibernate Criteria API (org.hibernate.Criteria) has been removed.

See here for more information.

Symptoms

Compilation failure.

Actions

Migrate existing code that relies on legacy Hibernate Criteria API to the JPA Criteria API.

Type system changes

Affected applications

Any application relying on custom types.

Such application would include classes that implement BasicType, UserType, CompositeUserType, JavaTypeDescriptor, SqlTypeDescriptor or similar, or register such classes through custom dialects or @TypeDef, or refer to such classes with @Type/@CollectionId#type/@AnyMetaDef#metaType/@AnyMetaDef#idType.

Breaking changes

Custom type interfaces and abstract classes changed, requiring adjustments to implementations.

Type reference annotations changed (some were removed, some had their attributes change), requiring adjustments to explicit type uses.

See here for more information.

Symptoms

Compilation failure in custom type implementations and/or type references through @TypeDef/@Type/etc.

Actions
  1. Double-check that you still need custom types. Hibernate ORM 6 provides more built-in types than ever:

  2. If you still need a custom type, refer to this guide to find out the appropriate solution in Hibernate ORM 6.

Query syntax/behavior changes

Affected applications

Any application using JPQL/HQL/SQL strings for queries, or equivalent Criteria queries, and relying on the syntax elements listed below.

Breaking changes

Hibernate ORM 6 made a few changes to the HQL syntax:

  1. The optional from keyword (update from MyEntity e set e.attr = null) is now disallowed for update queries.

  2. Column names are no longer accepted in HQL queries: Hibernate ORM will only understand JPA attribute names (~Java field name), treating column names as unresolvable attributes.

  3. Comparing entities directly to IDs is no longer allowed, be it root entities (where myentity = :param) or associations (where myentity.association = :param).

  4. count() in native queries will now return a Long instead of a BigInteger.

  5. Collection pseudo-attributes such as .size, .elements, .indices, .index, .maxindex, .minindex, .maxelement, .minelement are not longer supported.

Symptoms

Runtime exceptions, either while parsing the query (for no-longer-supported syntax elements) or when converting the result type (for count()).

Actions
  1. Remove the from keyword from your update queries: update from MyEntity e set e.attr = nullupdate MyEntity e set e.attr = null.

  2. If entities have attributes mapped to columns with a different name (e.g. attribute myText mapped to column my_text), make sure to use the attribute name in HQL queries and never the column name.

  3. Reference identifiers explicitly instead of referencing entities where identifier comparisons are involved: where myentity = :paramwhere myentity.id = :param, where myentity.association = :paramwhere myentity.association.id = :param.

  4. Adjust your native queries returning count() to expect a result of type Long instead of BigInteger.

  5. Replace collection pseudo-attributes with the equivalent functions:

    • mycollection.sizesize(mycollection)

    • mycollection.elementsvalue(mycollection)

    • mycollection.indicesindex(mycollection) (for lists) or key(mycollection) (for maps)

    • mycollection.maxindexmaxindex(mycollection)

    • mycollection.minindexminindex(mycollection)

    • mycollection.maxelementmaxelement(mycollection)

    • mycollection.minelementminelement(mycollection)

Configuration

Dialect configuration changes

Affected applications

Any application configuring a Hibernate ORM dialect explicitly.

Such application would use the configuration property quarkus.hibernate-orm.dialect in application.properties/application.yaml, or the configuration property hibernate.dialect in persistence.xml.

Breaking changes

Hibernate ORM 6 is now able to handle multiple versions and spatial variants of a database within a single dialect class; as a result, version-specific and spatial-specific dialect classes such as org.hibernate.dialect.PostgreSQL91Dialect or org.hibernate.spatial.dialect.postgis.PostgisPG94Dialect are deprecated and will lead to warnings on startup.

See here for more information.

Additionally, some dialects have moved to a new Maven artifact, org.hibernate.orm:hibernate-community-dialects, because they are no longer supported in Hibernate ORM Core.

Finally, the Quarkus-specific H2 dialect io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect was removed as it is no longer necessary.

Symptoms
  • Deprecation warning about the dialect on startup.

  • Build or startup failure if the application attempts to use dialects that have been removed such as io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect or that moved to hibernate-community-dialects.

Actions
  1. Change the configuration property quarkus.hibernate-orm.dialect (in application.properties) / hibernate.dialect (in persistence.xml):

  2. Set the database version through configuration property quarkus.datasource.db-version (in application.properties) / jakarta.persistence.database-product-version (in persistence.xml).

Database schema and data serialization/deserialization changes

Best-effort Hibernate ORM 5.6 compatibility switch

If you are in a hurry and want to address API/behavior issues and keep (most) schema changes for later, you can take advantage of the Hibernate ORM 5.6 database compatibility switch:

quarkus.hibernate-orm.database.orm-compatibility.version = 5.6

When this property is set, Quarkus attempts to configure Hibernate ORM to exchange data with the database as the given version of Hibernate ORM would have, on a best-effort basis.

Warning
  • Schema validation may still fail in some cases: this attempts to make Hibernate ORM 6+ behave correctly at runtime, but it may still expect a different (but runtime-compatible) schema.

  • This does not address every breaking change. For example, older database versions that are no longer supported in Hibernate ORM Core will still not work correctly unless you take additional steps.

  • Robust test suites are still useful and recommended: you should still check that your application behaves as intended with your legacy schema.

  • This feature is inherently unstable: some aspects of it may stop working in future versions of Quarkus, and older versions will be dropped as Hibernate ORM changes pile up and support for those older versions becomes too unreliable.

  • You should still plan a migration of your schema to a newer version of Hibernate ORM. For help with migration, refer to the rest of this guide.

One identifier generator (sequence/table) per entity hierarchy instead of a single hibernate_sequence

Note
This is addressed by the 5.6 compatibility switch.
Affected applications
  • Any application defining an entity that extends PanacheEntity

  • Any application defining an entity that uses @GeneratedValue on an entity identifier without assigning an identifier generator explicitly (no @SequenceGenerator/@TableGenerator/@GenericGenerator).

Breaking changes

Hibernate ORM will now assign a distinct identifier generator to each entity hierarchy by default.

This means Hibernate ORM will expect a different database schema, where each entity hierarchy has its own sequence, for example, instead of a single hibernate_sequence for all entities.

See here for more information.

Symptoms
  • If schema validation is enabled, it will fail, mentioning missing or undefined sequences/tables, e.g. ERROR: relation "hibernate_sequence" does not exist.

  • Otherwise, SQL import scripts or persisting new entities will fail with exceptions referring to missing or undefined sequences/tables.

Actions
  1. Start your application with the setting quarkus.hibernate-orm.database.generation=validate to have Hibernate ORM log all schema incompatibilities, which will list all sequences that are missing. Depending on your mapping (e.g. if your mapping defines an identifier generator explicitly for each entity), there may not be any.

  2. Optionally, if the sequence/table names used by Hibernate ORM by default are not to your liking, configure the identifier generation of each entity explicitly: sequence, identity column or table. When using Panache, such (advanced) configuration requires extending PanacheEntityBase instead of PanacheEntity.

  3. Update your database schema:

    • Use DDL (SQL such as create sequence […​]) to create the missing sequences/tables.

    • Use DDL to initialize sequences/tables with the right value (essentially max(id) + 1).

  4. Update any initialization script (e.g. import.sql) to work with the per-entity sequences instead of hibernate_sequence.

Increment size for sequences defaults to 50 instead of 1

Note
This is addressed by the 5.6 compatibility switch.
Affected applications
  • Any application defining an entity that extends PanacheEntity

  • Any application defining an entity that uses @GeneratedValue on an entity identifier without assigning an identifier generator explicitly (no @SequenceGenerator/@TableGenerator/@GenericGenerator).

  • Any application defining an entity that uses @GeneratedValue on an entity identifier with the corresponding identifier generator defined with @GenericGenerator, using database sequences and an explicitly defined increment size.

Breaking changes

Hibernate ORM will now default to 50 instead of 1 for the increment size of sequences.

Upon retrieving the next value of a sequence, Hibernate ORM will pool the retrieved value as well as the 49 next ones to use them as identifier IDs (Quarkus defaults to the pooled-lo optimizer).

See here for more information.

Symptoms
  • If schema validation is enabled, it will fail, mentioning an incorrect increment size for your sequences (actual 1, expected 50).

  • Otherwise, SQL import scripts or persisting new entities will fail with exceptions referring to duplicate keys or violated primary key constraints.

Actions
  1. Update your database schema:

    • Use DDL (SQL such as alter sequence […​]) to set the increment size of your sequences to the right value, i.e. to the allocation size of your identifier generators, which defaults to 50 since Hibernate ORM 6.

  2. If your use data import scripts and your application/tests assume that certain entities have a certain identifier, do not simply use nextval('mysequence') as its may no longer return the value you expect. Just hardcode those entity identifiers in your data import scripts and reset the relevant sequences at the end of the script.

    E.g. this:

    INSERT INTO hero(id, name, otherName, picture, powers, level)
    VALUES (nextval('hibernate_sequence'), 'Chewbacca', '', 'https://www.superherodb.com/pictures2/portraits/10/050/10466.jpg',
            'Agility, Longevity, Marksmanship, Natural Weapons, Stealth, Super Strength, Weapons Master', 5);

    Should become this:

    -- Hardcode the ID of the first Hero to the value "1", since the tests expect that value.
    INSERT INTO hero(id, name, otherName, picture, powers, level)
    VALUES (1, 'Chewbacca', '', 'https://www.superherodb.com/pictures2/portraits/10/050/10466.jpg',
            'Agility, Longevity, Marksmanship, Natural Weapons, Stealth, Super Strength, Weapons Master', 5);
    -- Set the current value of the sequence so that the IDs of newly
    -- created entities won't conflict with the hardcoded identifiers above.
    ALTER SEQUENCE hero_seq RESTART WITH (select max(id) + 1 from hero);

Alternatively, if you do not wish to benefit from pooled identifier generation, set increment sizes explicitly in your mapping when you define identifier generators, using for example @SequenceGenerator(…​, allocationSize = 1) instead of @GenericGenerator. See here for more information.

enum, Duration, UUID, Instant, ZonedDateTime, OffsetDateTime, OffsetTime properties may be persisted/loaded differently

Note
This is addressed by the 5.6 compatibility switch, except for enum properties whose schema validation will fail but which should work correctly at runtime.
Affected applications

Any application with an entity property of the following types, unless it overrides the SQL type with @Type/@JdbcType:

  • any enum type, unless the property is annotated with @Enumerated(STRING)

  • Duration

  • UUID

  • Instant

  • ZonedDateTime

  • OffsetDateTime

  • OffsetTime

Breaking changes

Hibernate ORM will now map those Java types to different JDBC types, which depending on your database may map to a different SQL type:

  • enum types now map to SqlTypes.TINYINT, SqlTypes.SMALLINT or a native enum type depending on the number of values and database support for enums, instead of Types.TINYINT in Hibernate ORM 5.

  • Duration maps to SqlTypes.INTERVAL_SECOND, instead of Types.BIGINT in Hibernate ORM 5.

  • UUID maps to SqlTypes.UUID, instead of Types.BINARY in Hibernate ORM 5.

  • Instant maps to SqlTypes.TIMESTAMP_UTC, instead of Types.TIMESTAMP in Hibernate ORM 5.

  • ZonedDateTime and OffsetDateTime map to SqlTypes.TIMESTAMP_WITH_TIMEZONE instead of Types.TIMESTAMP in Hibernate ORM 5. Additionally, if the database does not properly store timezones, values are normalized to UTC upon persisting, instead of the Hibernate ORM 5 behavior that was to normalize to the timezone configured through quarkus.hibernate-orm.jdbc.timezone, defaulting to the JVM timezone.

  • OffsetTime maps to SqlTypes.TIME_WITH_TIMEZONE instead of Types.TIME in Hibernate ORM 5. Additionally, if the database does not properly store timezones, values are normalized to UTC upon persisting, instead of the Hibernate ORM 5 behavior that was to normalize to the timezone configured through quarkus.hibernate-orm.jdbc.timezone, defaulting to the JVM timezone.

Symptoms
  • If schema validation is enabled, it may fail, mentioning an incorrect type for your columns.

  • Otherwise:

    • For enum types, if your database supports native enum types (e.g. MySQL), persisting and loading entities will fail; otherwise they should work despite the discrepancy between your database schema and the one expected by Hibernate ORM.

    • For ZonedDateTime, OffsetDateTime and OffsetTime, if you were previously effectively using a normalization timezone (quarkus.hibernate-orm.jdbc.timezone or JVM timezone) different from UTC, values loaded from the database may be incorrect.

    • For all other types, persisting and loading should work correctly despite the discrepancy between your database schema and the one expected by Hibernate ORM.

Actions
  1. Start your application with the setting quarkus.hibernate-orm.database.generation=validate to have Hibernate ORM log all schema incompatibilities, which will list all columns with an incorrect type.

  2. Update your database schema:

    • Use DDL (SQL such as alter table […​] alter column […​]) to set the type of your columns to the right value. Depending on your database this might require multiple steps, e.g. renaming the old column, creating the new column, copying the data from the old to the new column, and finally deleting the old column.

  3. For ZonedDateTime, OffsetDateTime and OffsetTime, if you were previously effectively using a normalization timezone (quarkus.hibernate-orm.jdbc.timezone or JVM timezone) different from UTC:

    • Use SQL UPDATE statements to convert data to the timezone expected by Hibernate ORM (UTC).

    • Alternatively, configure timezone storage explicitly through configuration property quarkus.hibernate-orm.mapping.timezone.default-storage; setting it to normalize will give you Hibernate ORM 5’s behavior.

Some databases and older database versions are no longer supported in Hibernate ORM

Warning
This is not addressed by the 5.6 compatibility switch.
Affected applications

Any application configuring a Hibernate ORM dialect explicitly, and using a dialect that targets either a unsupported database or an unsupported version of a supported database (see below for databases and versions supported in Hibernate ORM).

Such application would use the configuration property quarkus.hibernate-orm.dialect in application.properties/application.yaml, or the configuration property hibernate.dialect in persistence.xml.

Breaking changes

Hibernate ORM 6 dropped official support for some databases, either fully or only for older versions.

Note

Quarkus itself has stricter database and version restrictions. In Quarkus 3.0, we expect the following databases with the following minimum version to work out-of-the-box:

  • DB2 10.5 and later.

  • Apache Derby 10.14 and later.

  • MariaDB 10.6 and later.

  • Microsoft SQL Server 2016 and later.

  • MySQL 8 and later.

  • Oracle 12 and later.

  • PostgreSQL 10.0 and later.

  • H2 with the exact version defined in the Quarkus BOM.

Other database and versions may or may not work, may require additional configuration, and may suffer from limitations such as not working in native mode.

Additionally, upgrades to newer database versions may involve schema changes, as Hibernate ORM dialects targeting newer database versions may have different default schema expectations:

  • MariaDB:

    • MariaDBDialect uses sequences by default for identifier generation, which is compatible with MariaDB106Dialect (the default in Quarkus 2) and MariaDB103Dialect, but not compatible with MariaDB102Dialect (which uses ID generation tables).

  • Other databases:

    • No information at the moment. Feel free to open an issue to suggest more information to add to this guide.

Symptoms
  • Deprecation warning about the dialect on startup.

  • Build or startup failure if the application attempts to use dialects that moved to hibernate-community-dialects.

Actions
  1. Follow instructions to configure your dialect and database version for backwards-compatible behavior.

  2. If you use an older version of a supported database, seriously consider upgrading because your application might stop working at any moment with the next versions of Quarkus. This may involve changes to your database schema:

    • MariaDB < 10.2 to 10.3 and newer:

      1. Optionally, configure the identifier generation of each entity explicitly: sequence, identity column or table.

      2. Start your application with the setting quarkus.hibernate-orm.database.generation=validate to have Hibernate ORM log all schema incompatibilities, which will list all sequences that are missing. Depending on your mapping (e.g. if your mapping defines an identifier generator explicitly for each entity), there may not be any.

      3. Update your database schema:

        • Use DDL (SQL such as create sequence […​]) to create the missing sequences.

        • Use DDL to initialize sequences with the right value (essentially max(id) + 1).

    • Other databases:

      • No information at the moment. Feel free to open an issue to suggest more information to add to this guide.

Current Version

Migration Guide 3.16

Next Version

Migration Guide 3.17

Clone this wiki locally