- Jakarta Persistence
- Java 11
- Reading from JDBC
- Generated SQL
- Bulk SQM against entities mapped to multiple tables
- Identifier as Object
- @IdGeneratorType
- Type system
- Query
- SQM (HQL/Criteria)
- JPA Criteria behavior change
- NativeQuery
- ProcedureCall / StoredProcedureQuery Parameters
- Interceptor
- Removals
- Width-first fetch determination
- Restructuring of
org.hibernate.loader
- Restructuring of the sql package
- Deprecation of hbm.xml mappings
This guide discusses migration from Hibernate ORM version 6.0. For migration from earlier versions, see any other pertinent migration guides as well.
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.
|
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
-
Column aliases are no longer generated.
-
Column references are "unique-d".
-
Better definition of joins
-
Better determination of unnecessary joins (secondary tables, inheritance tables)
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
.
6.0 adds a new @IdGeneratorType
annotation that allows better, type-safe way
to define custom generators to use for identifier generation.
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.
The interface org.hibernate.type.descriptor.java.JavaTypeDescriptor
has been renamed to
org.hibernate.type.descriptor.java.JavaType
The interface org.hibernate.type.descriptor.sql.SqlTypeDescriptor
has been renamed to
org.hibernate.type.descriptor.jdbc.JdbcType
.
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.
See https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#basic for details.
UserType
is still supported, and is specified using the new Type
annotation.
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
orN
. Replaces the removedYesNoBooleanType
(yes_no
) TrueFalseConverter
-
Handles values stored in the database as either
T
orF
. Replaces the removedTrueFalseBooleanType
(true_false
) NumericBooleanConverter
-
Handles values stored in the database as either
1
or0
. Replaces the removedNumericBooleanType
(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.
Mapping of embeddables had a few changes as well.
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
6.0 introduces the new EmbeddableInstantiator
contract.
EmbeddableInstantiator
supports constructor-injection! Note, however, that embeddables used as
identifiers cannot use constructor injection.
6.0 defines 2 main ways to influence collection mapping @CollectionType
and @CollectionTypeRegistration
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.
See https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#collection-type-ann
for details of using @CollectionType
Allows to "auto apply" a UserCollectionType
whenever Hibernate encounters a particular
plural attribute classification
See https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#collection-type-reg-ann
for details of using @CollectionTypeRegistration
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.
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.
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
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.
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();
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
.
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.
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)
.
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 )
orkey( pluralAttribute )
function instead - index
-
The collection index can be referred to by using the
index( pluralAttribute )
orkey( 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
As NativeQuery
extends from Query
, all the changes listed in Query also apply
to NativeQuery
.
Some additional changes apply specifically to NativeQuery
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 ) );
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();
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
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
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.
Hibernate no longer provides built-in support for integrating itself with JMX environments.
Hibernate no longer provides built-in support for integrating itself with JACC environments.
-
'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'
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.
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
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.
Legacy hbm.xml
mapping format is considered deprecated and will no longer supported beyond 6.x.