Skip to content

Latest commit

 

History

History
702 lines (458 loc) · 23.2 KB

migration-guide.adoc

File metadata and controls

702 lines (458 loc) · 23.2 KB

6.0 Migration Guide

This guide discusses migration from Hibernate ORM version 6.0. For migration from earlier versions, see any other pertinent migration guides as well.

Jakarta Persistence

6.0 moves from Java Persistence as defined by the Java EE specs to Jakarta Persistence as defined by the Jakarta EE spec. The most immediate impact of this change is that applications would need to be updated to use the Jakarata Persistence classes (jakarta.persistence.) instead of the Java Persistence ones (javax.persistence.).

The Jakarta spec also renames the JPA settings (again, from javax.persistence. to 'jakarta.persistence.') and defines a new set of XSD namespaces for orm.xml and persistence.xml files.

Jakarta provides a transformer tool which, along with appropriate "rules", will transform a project from Java Persistence to Jakarta Persistence. This can update package names in source, settings, xsd references and more.

Note
As far as the XSD and setting changes, Hibernate does support both sets as a temporary aid in migration. It logs a deprecation warning when the Java EE variants are used. See the rules/ directory in the project root for the configuration used to migrate Hibernate itself.

Java 11

With 6.0, Hibernate ORM has moved to expect Java 11 as its baseline version

Reading from JDBC

One of the main reasons for 6.0 development was the move from reading results from the JDBC ResultSet by name (read-by-name) as done in previous versions of Hibernate, to reading the results by position (read-by-position).

Throughput testing of Hibernate showed that its use of read-by-name was its limiting factor in any further scaling in terms of throughput - much of the issue was actually the call into the ResultSet. We like to improve performance all the time :)

This change, along with Generated SQL, helped achieve this goal.

As discussed in Type system though, this change has a very big impact on Hibernate’s mapping type system

Generated SQL

  1. Column aliases are no longer generated.

  2. Column references are "unique-d".

  3. Better definition of joins

  4. Better determination of unnecessary joins (secondary tables, inheritance tables)

Bulk SQM against entities mapped to multiple tables

Identifier as Object

Previous versions of Hibernate required that all identifier types implement Serializable. 6.0 removes this restriction - identifiers can be any Object.

This change affects many api and spi methods previously defined using Serializable.

@IdGeneratorType

6.0 adds a new @IdGeneratorType annotation that allows better, type-safe way to define custom generators to use for identifier generation.

Type system

Another change is to generally modernize Hibernate’s mapping annotations and make them more type-safe.

We decided this is the right time since 6.0 is a major release and most of the type-related contracts were already changing to implement the read-by-position changes.

One part of this work was the removal of various String-based approaches for specifying Types to use from annotations, including the removal of @Type, @AnyMetaDef, @AnyMetaDefs, @MapKeyType, @TypeDef` and @TypeDefs, as well as removing annotation attributes accepting the type to use as a String (e.g. org.hibernate.annotations.CollectionType#type)

The User Guide covers the details of mapping your domain model.

Renaming of JavaTypeDescriptor contract

The interface org.hibernate.type.descriptor.java.JavaTypeDescriptor has been renamed to org.hibernate.type.descriptor.java.JavaType

Renaming of SqlTypeDescriptor contract

The interface org.hibernate.type.descriptor.sql.SqlTypeDescriptor has been renamed to org.hibernate.type.descriptor.jdbc.JdbcType.

Basic types

Basic mappings are no longer configurable through the BasicType contract. Instead, users configure the different aspects of mapping the basic value to the database -

  • JavaType

  • JdbcType

  • BasicValueConverter [1]

  • MutabilityPlan

This also made the various implementations of BasicType obsolete, thus they have been removed. NamedBasicTypeImpl takes the role of all the previous specific implementations by wrapping a JdbcType and JavaType.

The StandardBasicTypes class previously exposed BasicType instance fields, which now have been replaced with fields of the type BasicTypeReference. APIs that previously accepted just a BasicType have been adapted to also accept a BasicTypeReference which allows for uses of StandardBasicType fields to stay mostly source compatible.

UserType

UserType is still supported, and is specified using the new Type annotation.

Boolean converters

Hibernate now provides standard AttributeConverter implementations for handling different database representations as boolean values in the domain model:

YesNoConverter

Handles values stored in the database as either Y or N. Replaces the removed YesNoBooleanType (yes_no)

TrueFalseConverter

Handles values stored in the database as either T or F. Replaces the removed TrueFalseBooleanType (true_false)

NumericBooleanConverter

Handles values stored in the database as either 1 or 0. Replaces the removed NumericBooleanType (numeric_boolean)

E.g.

@Type(type="yes_no")
boolean isActive;

becomes

@Convert(converter=YesNoConverter.class)
boolean isActive;

In fact, if your application consistently maps booleans to the same database representation you can even register one as an auto-apply converter.

Embeddables / components

Mapping of embeddables had a few changes as well.

Different embeddable mappings

Multiple component mappings for the same Java class with different property mappings is no longer supported. Every property mapping combination should have its own Java class

EmbeddableInstantiator

6.0 introduces the new EmbeddableInstantiator contract.

EmbeddableInstantiator supports constructor-injection! Note, however, that embeddables used as identifiers cannot use constructor injection.

Plural attributes

6.0 defines 2 main ways to influence collection mapping @CollectionType and @CollectionTypeRegistration

@CollectionType

The @CollectionType annotation is kept from 5.x. However, where it used to define

String type();

it now defines

Class<? extends UserCollectionType> type();

The type to use must be a UserCollectionType (can no longer be a CollectionType) and it no longer works with type-definitions. See Type system for further discussion of general type changes.

@CollectionTypeRegistration

Allows to "auto apply" a UserCollectionType whenever Hibernate encounters a particular plural attribute classification

List as BAG

Historically, Hibernate treated List attributes with no explicit ordering details using its BAG semantics.

@ElementCollection
List<String> names;

This is not the most natural decision and is rooted in the original annotation support being too closely related to the hbm.xml mapping format.

Starting in 6.0, Hibernate now makes the more natural interpretation of such a mapping using its LIST semantics. It treats the above example with an implicit an @OrderColumn. The above mapping is now equivalent to

@ElementCollection
@OrderColumn(...)
List<String> names;

A new "compatibility flag" has been added to allow continuing with the legacy behavior:

hibernate.mapping.default_list_semantics=bag

The same can be achieved for specific attributes using the new @Bag annotation as well.

Misc

  • The default type for Duration was changed to NUMERIC which could lead to schema validation errors

Query

Stream

jakarta.persistence.Query#getResultStream() and org.hibernate.query.Query#stream() no longer return a Stream decorator. In order to close the underlying IO resources, it is now necessary to explicitly call the Stream#close() method.

This change makes the Streams returned by Hibernate behave as defined in the JDK Stream documentation, which is quite explicit about the need for an explicit call to close by the user to avoid resource leakages.

Iterate

The Query#iterate() method has been removed. The alternative is to use one of

  • Query#stream()

  • Query#getResultStream()

  • Get the Iterator from List returned by Query#list() / Query#getResultList()

SQM (HQL/Criteria)

Another major change in 6.0 is the move to a dedicated tree structure to model HQL and Criteria queries. This tree structure is called the Semantic Query Model, or SQM for short.

todo (6.0) - cover functions todo (6.0) - cover new temporal capabilities todo (6.0) - cover new syntaxes todo (6.0) - cover bulk manipulation query handling

JPA Criteria behavior change

Without JPA compliance (hibernate.jpa.compliance) or when the newly introduced hibernate.jpa.compliance.criteria_copy configuration property is disabled, it is expected that criteria queries passed to jakarta.persistence.EntityManager#createQuery(CriteriaQuery), jakarta.persistence.EntityManager#createQuery(CriteriaUpdate) or jakarta.persistence.EntityManager#createQuery(CriteriaDelete) are not mutated afterwards to avoid the need for copying the criteria query.

Prior to 6.0, mutations to criteria queries didn’t affect Query instances created from that. To retain backwards compatibility, enable the hibernate.jpa.compliance.criteria_copy configuration property.

Result "rows"

Queries that use joins without specifying a select clause (e.g. from Person p join p.address) used to return a List<Object[]>. Starting with 6.0, such a query instead returns List<Person>

The HQL query select p, a from Person p join p.address a returns instead a List<Object[]>.

List<Person> result = session.createQuery("from Person p join p.address").list();
List<Object[]> results = session.createQuery("select p, a from Person p join p.address a").list();

Pass-through tokens

The use of plain HQL identifiers in e.g. functions which couldn’t be interpreted as an attribute of a FROM root were passed through as-is to SQL in Hibernate 5.x which was dropped in 6.0 because we believe this is unsafe and might lead to surprising results. HQL queries that relied on this, need to be changed and use the newly introduced sql function, which allows passing through the content of a string literal to SQL.

An HQL query like select substring( e.description, 21, 11, octets ) from AnEntity e, which relies on this for passing through octets can be migrated to select substring( e.description, 21, 11, sql('octets') ) from AnEntity e.

DISTINCT

Starting with Hibernate ORM 6 it is no longer necessary to use distinct in JPQL and HQL to filter out the same parent entity references when join fetching a child collection. The returning duplicates of entities are now always filtered by Hibernate.

Which means that for instance it is no longer necessary to set QueryHints#HINT_PASS_DISTINCT_THROUGH to false in order to skip the entity duplicates without producing a distinct in the SQL query.

From Hibernate ORM 6, distinct is always passed to the SQL query and the flag QueryHints#HINT_PASS_DISTINCT_THROUGH has been removed.

Association Comparisons

Previously Hibernate did allow comparing an association with an FK value like …​ where alias.association = 1 or …​ where alias.association = alias.association.id or even …​ where alias.association = :param where param is bound to an integer 1. This was supported prior to Hibernate 6.0 if the foreign key for the association is an integer.

The right way to do this is de-referencing the association by the FK attribute …​ where alias.association.id = 1 which is guaranteed to not produce a join, or use an entity reference for …​ where alias.association = :param where param is bound to entityManager.getReference(EntityClass.class, 1).

Collection psuedo-attributes

Prior to 6.0, it was possible to de-reference special properties on plural attributes like size which was dropped. The special properties lead to confusion and were sometimes ambiguous. The replacement is the function syntax.

size

The collection size can be determined by using the size( pluralAttribute ) function instead

elements

The collection elements can be referred to by using the value( pluralAttribute ) function instead

indices

The collection indices can be referred to by using the index( pluralAttribute ) or key( pluralAttribute ) function instead

index

The collection index can be referred to by using the index( pluralAttribute ) or key( pluralAttribute ) function instead

maxindex

The collection maximum index can be determined by using the maxindex( pluralAttribute ) function instead

minindex

The collection minimum index can be determined by using the minindex( pluralAttribute ) function instead

maxelement

The collection maximum element can be determined by using the maxelement( pluralAttribute ) function instead

minelement

The collection minimum element can be determined by using the minelement( pluralAttribute ) function instead

NativeQuery

As NativeQuery extends from Query, all the changes listed in Query also apply to NativeQuery.

Some additional changes apply specifically to NativeQuery

Ordinal Parameters binding

HQL ordinal parameter binding is 1-based, this means that queries like

s.createQuery( "select p from Parent p where id in ?0", Parent.class );
query.setParameter( 0, Arrays.asList( 0, 1, 2, 3 ) );

that uses a 0-based positional binding are not supported, and they should be changed to the following

s.createQuery( "select p from Parent p where id in ?`", Parent.class );
query.setParameter( 1, Arrays.asList( 0, 1, 2, 3 ) );

Callable via NativeQuery

Using NativeQuery to call SQL functions and procedures is no longer supported. org.hibernate.procedure.ProcedureCall or jakarta.persistence.StoredProcedureQuery should be used instead.

@NamedNativeQuery references defining execution of procedure or functions should be migrated to use @NamedStoredProcedureQuery instead.

E.g., the following @NamedNativeQuery -

@NamedNativeQuery(
    name = "personAndPhones",
    query = "{ ? = call fn_person_and_phones( ? ) }",
    callable = true,
    resultSetMapping = "personWithPhonesResultMapping"
)

...

final List<Object[]> personAndPhones = entityManager
        .createNamedQuery("personAndPhones" )
        .setParameter( 1, 1L )
        .getResultList();

should be changed to use @NamedStoredProcedureQuery instead -

@NamedStoredProcedureQuery(
    name = "personAndPhones",
    procedureName = "fn_person_and_phones",
    resultSetMapping = "personWithPhonesResultMapping",
    hints = @QueryHint(name = "org.hibernate.callableFunction", value = "true"),
    parameters = @StoredProcedureParameter(type = Long.class)
)

Either org.hibernate.procedure.ProcedureCall or jakarta.persistence.StoredProcedureQuery can be used to execute the named query -

// Use StoredProcedureQuery
final List<Object[]> personAndPhones = entityManager
        .createNamedStoredProcedureQuery( "personAndPhones" )
        .setParameter( 1, 1L )
        .getResultList();

// Use ProcedureCall
final List<Object[]> personAndPhones = entityManager
        .unwrap( Session.class )
        .getNamedProcedureCall( "personAndPhones" )
        .setParameter( 1, 1L )
        .getResultList();

It is also no longer supported to execute procedures and functions via a dynamic (unnamed) NativeQuery. All such usages should be converted to use ProcedureCall or StoredProcedureQuery instead via Session#createStoredProcedureCall or EntityManager#createStoredProcedureQuery, respectively.

// Use StoredProcedureQuery
final List<Object[]> personAndPhones = entityManager
        .createStoredProcedureQuery( "fn_person_and_phones", "personWithPhonesResultMapping" )
        .setParameter( 1, 1L )
        .getResultList();

// Use ProcedureCall
final List<Object[]> personAndPhones = entityManager
        .unwrap( Session.class )
        .createStoredProcedureCall( "fn_person_and_phones", "personWithPhonesResultMapping" )
        .setParameter( 1, 1L )
        .getResultList();

ProcedureCall / StoredProcedureQuery Parameters

For parameters defined on a ProcedureCall as accepting binding (IN and INOUT), a distinction is now made between whether setParameter is called or not. If setParameter was called, whatever value was set by the user is passed to the database. If it was not called, Hibernate will not set any value which triggers the default value defined on the database procedure argument be used

Interceptor

The signature of the #onSave method has been changed from

boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types)

to

boolean onSave(Object entity, Object id, Object[] state, String[] propertyNames, Type[] types)

to account for the general change in expected identifier type from Serializable to Object. See Identifier as Object.

If custom Interceptor implementations do not use @Override on their implementations, this can lead to situations where a custom Interceptor no longer overrides this method. Moral of the story…​ always use @Override - this is why it exists

Removals

Legacy Hibernate Criteria API

The legacy Hibernate Criteria API which was deprecated back in Hibernate 5.x and removed in 6.0. Usually, all queries using the legacy API can be modeled with the JPA Criteria API. In some cases it is necessary to use the Hibernate JPA Criteria extensions.

JMX integration

Hibernate no longer provides built-in support for integrating itself with JMX environments.

JACC integration

Hibernate no longer provides built-in support for integrating itself with JACC environments.

Previously Deprecated features:

  • 'hibernate.classLoader.application', 'hibernate.classLoader.resources', 'hibernate.classLoader.hibernate' and 'hibernate.classLoader.environment': use 'hibernate.classLoaders' instead.

  • 'hibernate.hbm2dll.create_namespaces': use 'jakarta.persistence.create-database-schemas' or 'hibernate.hbm2ddl.create_namespaces'

Width-first fetch determination

Previous versions of Hibernate determined fetches using a depth-first approach, which occasionally led to odd "circularity" determination. Starting with 6.0, we now perform fetch determination using a width first approach.

As back-ground, Hibernate does not always know that a fetch is truly circular. So it uses the approach that seeing the same table and column(s) as keys might be a circularity and stops processing fetches using that table/column(s) combination.

Given a model such as

@Entity
class Node {

    @ManyToOne

    Node node1;

    @ManyToOne
    Node node2;

}

Hibernate previously walked the graph for the Node#node1 sub-tree prior to walking the Node#node2 sub-tree

being all eager we are executing a query with 4 joins

FROM Node
JOIN Node.node1
JOIN Node.node1.node2
JOIN Node.node2
JOIN Node.node2.node1

whereas before we

FROM Node
JOIN Node.node1
JOIN Node.node1.node2

and issue a select for Node.node2 if the FK of Node.node2 is not null

FROM Node.node2
JOIN Node.node2.node1
JOIN Node.node2.node1.node2

In this simple example this is not such a big deal, but if we increase the number of eager fetched self-associations to e.g. 3 like here:

@Entity
class Node {

    @ManyToOne
    Node node1;

    @ManyToOne
    Node node2;

    @ManyToOne
    Node node3;

}

this results in mind-blowing 15 joins

FROM Node
JOIN Node.node1
JOIN Node.node1.node2
JOIN Node.node1.node2.node3
JOIN Node.node1.node3
JOIN Node.node1.node3.node2
JOIN Node.node2
JOIN Node.node2.node1
JOIN Node.node2.node1.node3
JOIN Node.node2.node3
JOIN Node.node2.node3.node1
JOIN Node.node3
JOIN Node.node3.node1
JOIN Node.node3.node1.node2
JOIN Node.node3.node2
JOIN Node.node3.node2.node1

as you can see, this leads to a lot of joins very quickly, but the behavior of 5.x simply was not intuitive. To avoid creating so many joins, and also in general, we recommend that you use lazy fetching i.e. @ManyToOne(fetch = FetchType.LAZY) or @OneToOne(fetch = FetchType.LAZY) for most associations, but this is especially important if you have multiple self-referencing associations as you can see in the example.

Restructuring of org.hibernate.loader

The contents of the loader.collection package were restructured into loader.ast.spi and loader.ast.internal as well as adapted to the SQM API.

The contents of loader.custom were adapted and moved to query.sql.

The contents of loader.entity and loader.plan were removed

Restructuring of the sql package

The contents of sql.ordering were adapted and moved to metamodel.mapping.ordering.ast.

Classes of the sql package that were previously used for building SQL, but aren’t needed anymore, were removed. The SQL generation is now fully handled through the SqlAstTranslator which a Dialect exposes a factory for.

Deprecation of hbm.xml mappings

Legacy hbm.xml mapping format is considered deprecated and will no longer supported beyond 6.x.


1. Think `AttributeConverter`