- Java 11
- Jakarta Persistence
- Reading from JDBC
- Generated SQL
- Identifier as Object
- @IdGeneratorType
- Implicit Identifier Sequence and Table Name
- Defaults for implicit sequence generators
- Type system
- Query
- Interceptor
- Fetch circularity determination
- Restructuring of
org.hibernate.loader
- Restructuring of the sql package
- Deprecation of hbm.xml mappings
- Association laziness now respected
- hbm.xml <return-join/> behavior change
- Removals
- Configuration property renames
- Dialects
- Multitenancy simplification
- @Where annotation
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.
The way in which Hibernate determines implicit names for sequences and tables associated with identifier generation has changed in 6.0 which may affect migrating applications.
As of 6.0, Hibernate by default creates a sequence per entity hierarchy instead of a single sequence hibernate_sequence
.
Due to this change, users that previously used @GeneratedValue(strategy = GenerationStrategy.AUTO)
or simply @GeneratedValue
(since AUTO
is the default),
need to ensure that the database now contains sequences for every entity, named <entity name>_seq
. For an entity Person
, a sequence person_seq
is expected to exist. It’s best to run hbm2ddl (e.g. by temporarily setting hbm2ddl.auto=create
) to obtain a list of DDL statements for the sequences.
Users that use a import.sql
file to import test or static data during application boot or for tests have to make sure the new sequences match expectations of Hibernate. If a import.sql
file contains references to the hibernate_sequence
, these have to be replaced with <entity name>_seq
. Also note that the default allocation size changed for such implicit sequences.
To help with backwards compatibility, or to apply any general naming strategy, 6.0 introduces the
org.hibernate.id.enhanced.ImplicitDatabaseObjectNamingStrategy
contract which can be specified using
the hibernate.id.db_structure_naming_strategy
setting. See discussion at
link:https://docs.jboss.org/hibernate/orm/6.0/javadocs/org/hibernate/cfg/AvailableSettings.html#ID_DB_STRUCTURE_NAMING_STRATEGY
For backwards compatibility, use either hibernate.id.db_structure_naming_strategy=single
or
hibernate.id.db_structure_naming_strategy=legacy
depending on needs
Implicit sequences, like the hibernate_sequence
before, now respect the defaults of the JPA @SequenceGenerator
annotation,
which means that the sequences have an allocation size of 50.
Users that use a import.sql
file to import test or static data during application boot or for tests have to make sure the new sequences match expectations of Hibernate. If a import.sql
file contains inserts to a table, the sequence for that table has to be reset to <max id value of table> + 1 + <allocation size>
. This is because Hibernate interprets the value returned by the sequence as hi-value for its pooled optimizer. An alternative is to reset the sequence to <max id value of table> + 1
and also configure the sequence generator to that initial value, by specifying @SequenceGenerator(sequenceName = "<entity name>_seq", initialValue = <max id value of table> + 1)
on every entity.
Users are recommended to do the former and add statements like alter sequence <entity name>_seq restart with <max id value of table> + 1 + <allocation size>
e.g. alter sequence person_seq restart with 53
if id values 1 and 2 are used.
To help with backwards compatibility, or to apply any general naming strategy, 6.0 introduces the
org.hibernate.id.enhanced.ImplicitDatabaseObjectNamingStrategy
contract which can be specified using
the hibernate.id.db_structure_naming_strategy
setting. See discussion at
link:https://docs.jboss.org/hibernate/orm/6.0/javadocs/org/hibernate/cfg/AvailableSettings.html#ID_DB_STRUCTURE_NAMING_STRATEGY
For backwards compatibility, use hibernate.id.db_structure_naming_strategy=legacy
.
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 @AnyMetaDef
, @AnyMetaDefs
, @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.
The CompositeUserType
interface was re-implemented to be able to model user types as proper embeddable types.
A major difference to 5.x is the introduction of an "embeddable projection" that is used to determine the mapping structure.
Previously, a CompositeUserType
had to provide property names and types through dedicated accessor methods,
but this was complicated for non-basic mappings and required quite some knowledge about Hibernate internals.
With 6.0 these methods are replaced with a method that returns an "embeddable projection" class.
The class is like a regular @Embeddable
class and is used to determine the mapping structure for the CompositeUserType
.
Component values of a user type object are accessed by property index. The property index is 0-based and can be determined by sorting the persistent attribute names lexicographically ascending and using the 0-based position as property index.
For example, the following component:
public class MonetaryAmountEmbeddable {
BigDecimal value;
Currency currency;
}
will assign property index 0 to currency
and index 1 to value
.
Note that it is not possible anymore to use @Columns
to specify the names of columns of a composite user type mapping.
Since a CompositeUserType
now constructs a proper component, it is necessary to use the @AttributeOverride
annotation.
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
The type code SqlType.BIGINT
is now mapped to the Java type Long
by default; it previously was (incorrectly) mapped to BigInteger
.
As a result, native queries whose "select" clause returns a BIGINT
element will now convert that element to Long
,
instead of BigInteger
previously. This will be the case for native queries returning a count()
in particular.
Duration now maps to the type code SqlType.INTERVAL_SECOND
by default, which maps to the SQL type interval second
if possible, and falls back to numeric(21)
.
In either case, schema validation errors could occur as 5.x used the type code Types.BIGINT
.
Migration to numeric(21)
should be easy. The migration to interval second
might require a migration expression like
cast(cast(old as numeric(21,9) / 1000000000) as interval second(9))
.
To retain backwards compatibility, configure the setting hibernate.type.preferred_duration_jdbc_type
to BIGINT
.
UUID now maps to the type code SqlType.UUID
by default, which maps to the SQL type uuid
if possible, and falls back to binary(16)
.
Due to the change to the native uuid
type, schema validation errors could occur on database with native data type support.
The migration to uuid
might require a migration expression like cast(old as uuid)
.
To retain backwards compatibility, configure the setting hibernate.type.preferred_uuid_jdbc_type
to BINARY
.
Instant now maps to the type code SqlType.TIMESTAMP_UTC
by default, which maps to the SQL type timestamp with time zone
if possible, and falls back to timestamp
.
Due to this change, schema validation errors could occur on some databases.
The migration to timestamp with time zone
might require a migration expression like cast(old as timestamp with time zone)
.
To retain backwards compatibility, configure the setting hibernate.type.preferred_instant_jdbc_type
to TIMESTAMP
.
Quite a few changes have been made to how Query works.
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.
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
The implementations for bulk SQM DML statements like insert
, update
and delete
were significantly improved in 6.0.
An important bug fix is, that delete
statements now properly clean up collection tables.
insert
statements now also support inserting into multi-table entities by making use of special purpose temporary tables
into which the insert goes and is then split up into the respective tables.
There are currently 2 implementation strategies:
-
Using temporary tables (the default)
-
Using DML in CTEs (used on DB2 and PostgreSQL)
The temporary table approach is pretty simple and works in a similar way to how 5.x already implemented it. Data or primary key values are first inserted into a temporary table and then the DML changes are applied to the various tables that are affected by the SQM DML statement.
The CTE approach is new and implements a more performant approach by executing a single statement, containing the various individual DML statements that would normally be executed separately. This allows to run SQM DML statements in a single JDBC operation that does not move any data between the database and the application, which should provide a significant boost for statements that involve many rows.
Note that the configuration property hibernate.hql.bulk_id_strategy
was changed to hibernate.query.mutation_strategy
which will now refer to classes or objects implementing org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy
.
By default, when bootstrapping Hibernate through the native bootstrap APIs or when explicitly disabling the newly introduced
hibernate.criteria.copy_tree
configuration property, 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.criteria.copy_tree
configuration property.
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
.
In Hibernate 5, the optional keyword FROM
was allowed for update
statements like update from MyEntity e set e.attr = null
.
Support for this was removed to more align with JPQL. Just remove the from
keyword from your existing update
statements.
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 compare two paths of different types or comparing an entity path against a literal e.g.:
session.createQuery( "select a from MyEntity a, MyEntity b where a = b.id" )
or
session.createQuery( "select a from MyEntity a where a = 123")
This is not allowed anymore. The queries have to be changed to
session.createQuery( "select a from MyEntity a, MyEntity b where a = b" )
and
session.createQuery( "select a from MyEntity a where a.id = 123");
The same is true for the left and right hand side types of a comparison predicate. Queries like
Root<Participation> root = criteria.from( Participation.class );
Root<Submission> rootSubQuery = subQuery.from( Submission.class );
subQuery.select( rootSubQuery.join( "submitters" ) );
// left hand side of type Submission.id and right hand side of Submissions types
criteria.where( root.get( "id" ).in( subQuery ) );
will be considered invalid and should be changed into
Root<Participation> root = criteria.from( Participation.class );
Root<Submission> rootSubQuery = subQuery.from( Submission.class );
subQuery.select( rootSubQuery.join( "submitters" ) );
// left hand side of type Submission.id and right hand side of Submissions types
criteria.where( root.in( subQuery ) );
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 ) );
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
Another change in 6.0 is related to the query result cache.
In previous versions, when the query-cache is enabled and a query returning entities is executed, only the entity identifiers were stored in the query-cache. If second-level caching is enabled for a returned entity, the entity data was stored in its second-level cache region.
Storing just the identifiers in the query-cache has a major drawback when fetching is defined for the query (dynamic fetch, entity-graph, etc) as it can, and often does, lead to N+1 selects.
Starting in 6.0, we now store the complete set of data for the entity into the query-cache. This also can have a drawback related to the size of caches. We plan to address this further in later 6.x releases to allow storing just the identifiers along the lines of the previous behavior.
E.g.
Statistics stats = sessionFacroty.getStatistics();
// First time the query is executed, query and results are cached and both the query and entity chache will be populated.
TypedQuery<Employee> query = session.createQuery( "select e from Employee e", Employee.class )
.setHint( HINT_CACHEABLE, true );
List<Employee> employees = query.getResultList();
assertEquals( 1, employees.size() );
assertEquals( 0, stats.getQueryCacheHitCount() );
assertEquals( 1, stats.getQueryCacheMissCount() );
assertEquals( 1, stats.getQueryCachePutCount() ); // query cache is populated.
assertEquals( 0, stats.getSecondLevelCacheHitCount() );
assertEquals( 0, stats.getSecondLevelCacheMissCount() );
assertEquals( 1, stats.getSecondLevelCachePutCount() ); // entity cache is populated as well.
stats.clear();
// Second time the same query is executed only the query cache will be hit.
TypedQuery<Employee> query = session.createQuery( "select e from Employee e", Employee.class )
.setHint( HINT_CACHEABLE, true );
List<Employee> employees = query.getResultList();
assertEquals( 1, employees.size() );
assertEquals( 1, stats.getQueryCacheHitCount() ); // the query cache is hit.
assertEquals( 0, stats.getQueryCacheMissCount() );
assertEquals( 0, stats.getQueryCachePutCount() );
assertEquals( 0, stats.getSecondLevelCacheHitCount() );
assertEquals( 0, stats.getSecondLevelCacheMissCount() );
assertEquals( 0, stats.getSecondLevelCachePutCount() ); // No need to hit the entity cache because the query cache contains all the entity data.
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.
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
As back-ground, Hibernate does understand whether a fetch is actually, truly circular. It simply understands that while walking a fetch-graph it encounters the same table/column(s) making up a particular foreign-key. In this case, it simply stops walking the graph any deeper.
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.
Given a model such as
@Entity
class Node {
@ManyToOne
Node node1;
@ManyToOne
Node node2;
}
Hibernate previously generated joins by walking the entity association graph for the Node#node1
sub-tree prior to walking the Node#node2
sub-tree.
Since the associations are all eager, Hibernate 6.0 now executes a query with 4 joins
FROM Node
JOIN Node.node1
JOIN Node.node1.node2
JOIN Node.node2
JOIN Node.node2.node1
whereas before it executed
FROM Node
JOIN Node.node1
JOIN Node.node1.node2
and issued a select for Node.node2
if the FK of Node.node2
was 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 the number of eager fetched self-associations is increased 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.
Prior to Hibernate 6.0, lazy associations that used fetch="join"
or @Fetch(FetchMode.JOIN)
were considered eager
when loaded by-id i.e. through Session#get
/EntityManager#find
, even though for queries the association was treated as lazy.
Starting with Hibernate 6.0, the laziness of such associations is properly respected, regardless of the fetch mechanism.
Backwards compatibility can be achieved by specifying lazy="false"
or @ManyToOne(fetch = EAGER)
/@OneToOne(fetch = EAGER)
/@OneToMany(fetch = EAGER)
/@ManyToMany(fetch = EAGER)
.
As of Hibernate 6.0, a <return-join/>
will cause a fetch of an association, rather than adding a selection item.
Consider the following example:
<sql-query name="organizationreturnproperty">
<return alias="org" class="Organization">
<return-property name="id" column="ORGID"/>
<return-property name="name" column="NAME"/>
</return>
<return-join alias="emp" property="org.employments">
<return-property name="key" column="EMPLOYER"/>
<return-property name="element" column="EMPID"/>
<return-property name="element.employee" column="EMPLOYEE"/>
</return-join>
...
</sql-query>
Prior to 6.0, a query would return a list of tuples [Organization
, Employee
],
but now this will return a list of Organization
with an initialized employments
collection.
The following features have been removed
In 6.0 the support for basic property mappings with multiple columns was removed. The only use case for that was when a
CompositeUserType
was in use, which was reworked to now work on top of components.
Uses like:
<property name="salary" type="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<column name="CURRENCY"/>
<column name="AMOUNT" sql-type="float"/>
</property>
have to be migrated to proper components:
<component name="salary" class="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<property name="value" column="AMOUNT">
<type name="float"/>
</property>
<property name="currency" column="CURRENCY"/>
</component>
The component class attribute now supports interpreting a CompositeUserType
class properly.
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.
The Query#iterate()
method has been removed. The alternative is to use one of
-
Query#stream()
-
Query#getResultStream()
-
Get the
Iterator
fromList
returned byQuery#list()
/Query#getResultList()
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",
resultSetMappings = "personWithPhonesResultMapping",
hints = @QueryHint(name = "org.hibernate.callableFunction", value = "true"),
parameters = @StoredProcedureParameter(type = Long.class)
)
Callable named native queries in hbm.xml files should be migrated to the orm.xml version.
E.g., the following <sql-query callable="true">
-
<sql-query name="simpleScalar" callable="true">
<return-scalar column="name" type="string"/>
<return-scalar column="`value`" type="long"/>
{ ? = call simpleScalar(:number) }
</sql-query>
...
final List<Object[]> results = entityManager
.createNamedQuery("simpleScalar" )
.setParameter( 1, 1L )
.getResultList();
should be changed to use <named-stored-procedure-query/>
instead -
<named-stored-procedure-query name="simpleScalar" procedure-name="simpleScalar">
<parameter class="java.lang.Integer" mode="IN" name="number"/>
<result-set-mapping>simpleScalar</result-set-mapping>
<hint name="org.hibernate.callableFunction" value="true"/>
</named-stored-procedure-query>
<sql-result-set-mapping name="simpleScalar">
<column-result name="name" class="java.lang.String"/>
<column-result name="value" class="java.lang.Long"/>
</sql-result-set-mapping>
Tip
|
To ease the migration, <sql-query callable="true"/> and @NamedNativeQuery(callable = true) queries
will be translated and registered as named stored procedure in 6.0, but future versions will drop this automatic translation.
|
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( "simpleScalar" )
.setParameter( 1, 1L )
.getResultList();
// Use ProcedureCall
final List<Object[]> personAndPhones = entityManager
.unwrap( Session.class )
.getNamedProcedureCall( "simpleScalar" )
.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();
The fetch all properties
clause was removed from the HQL language without a replacement.
A similar behavior can be achieved by constructing an entity graph and applying that as load graph:
EntityGraph<Document> entityGraph = entityManager.createEntityGraph( Document.class );
for ( Attribute<Document, ?> attr : entityManager.getMetamodel().entity( Document.class ).getAttributes() ) {
entityGraph.addAttributeNodes( attr.getName() );
}
List<Document> documents = s.createQuery( "from Document", Document.class )
.setHint( "jakarta.persistence.loadgraph", entityGraph )
.getResultList();
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.
We no longer publishing zip and tgz bundles to SourceForge.
We now publish additional documentation artifacts, such as:
-
This Migration Guide
-
The generated logging report
-
The generated incubation report
-
The generated internals report
-
'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'
Some configuration properties that were deprecated for a long time were finally removed for consistency reasons:
Old property | New property |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
There are also some property prefixes where the deprecated variant was finally removed:
Old prefix | New prefix |
---|---|
|
|
|
|
|
|
We decided this is the right time since 6.0 is a major release.
As of Hibernate 6.0, some dialect classes that are maintained by vendors or individuals, as opposed to the Hibernate team.
have moved to a separate Maven artifact: org.hibernate.orm:hibernate-community-dialects
.
For more information, see https://github.com/hibernate/hibernate-orm/blob/6.0/dialects.adoc#community-dialects.
As of Hibernate 6.0, dialects can detect and adapt to the version of the database in use and its spatial capabilities.
As a result, version-specific dialects (e.g. org.hibernate.dialect.PostgreSQL91Dialect
)
or spatial-specific dialects (e.g. org.hibernate.spatial.dialect.postgis.PostgisPG94Dialect
) should no longer be used.
Use these dialects instead, and ignore their deprecated subclasses:
-
org.hibernate.dialect.CockroachDialect
-
org.hibernate.dialect.DB2Dialect
-
org.hibernate.dialect.DB2iDialect
-
org.hibernate.dialect.DB2zDialect
-
org.hibernate.dialect.DerbyDialect
-
org.hibernate.dialect.H2Dialect
-
org.hibernate.dialect.HANAColumnStoreDialect
-
org.hibernate.dialect.HANARowStoreDialect
-
org.hibernate.dialect.HSQLDialect
-
org.hibernate.dialect.MariaDBDialect
-
org.hibernate.dialect.MySQLDialect
-
org.hibernate.dialect.OracleDialect
-
org.hibernate.dialect.PostgreSQLDialect
-
org.hibernate.dialect.PostgresPlusDialect
-
org.hibernate.dialect.SpannerDialect
-
org.hibernate.dialect.SQLServerDialect
-
org.hibernate.dialect.SQLServerDialect
-
org.hibernate.dialect.SybaseAnywhereDialect
-
org.hibernate.dialect.SybaseASEDialect
The same recommendation applies for community-supported dialects; use these and not their deprecated subclasses:
-
org.hibernate.community.dialect.CacheDialect
-
org.hibernate.community.dialect.CUBRIDDialect
-
org.hibernate.community.dialect.FirebirdDialect
-
org.hibernate.community.dialect.IngresDialect
-
org.hibernate.community.dialect.InformixDialect
-
org.hibernate.community.dialect.MaxDBDialect
-
org.hibernate.community.dialect.MimerSQLDialect
-
org.hibernate.community.dialect.RDMSOS2200Dialect
-
org.hibernate.community.dialect.SQLiteDialect
-
org.hibernate.community.dialect.TeradataDialect
-
org.hibernate.community.dialect.TimesTenDialect
Multitenancy in Hibernate ORM has been simplified. Hibernate will now infer whether multitenancy is enabled or not automatically:
-
If a
MultiTenantConnectionProvider
is configured, Hibernate ORM will assume either database- or schema-based multitenancy (there is no difference between those two as far as Hibernate ORM is concerned). -
If an entity property is annotated with the new
@TenantId
annotation, Hibernate ORM will assume discriminator-based multitenancy (which is a new feature).
To migrate:
-
Remove any reference to the configuration property
hibernate.multiTenancy
(AvailableSettings.MULTI_TENANT
) or toMultiTenancyStrategy
from your application: they are no longer necessary.hibernate.multiTenancy
will be ignored andAvailableSettings.MULTI_TENANT
/MultiTenancyStrategy
will lead to compilation errors since they have been removed. -
If your application was somehow achieving discriminator-based multitenancy through custom code (e.g. custom filters), consider relying on the new
@TenantId
annotation and setting the session’s tenant ID instead.
The @Where annotation was always meant as a way of supporting "soft deletes",
allowing the records of an entity which do not respect a simple WHERE
clause to be completely ignored by Hibernate.
In Hibernate 5.x, this was true only when fetching entities or collections of entities having this annotation.
As of Hibernate 6.0, the clause specified by a @Where
applied to an entity type is used in all interactions Hibernate has the with that entity type. This includes, for instance, when deleting or updating records using mutation queries (e.g. DELETE FROM MyEntity where id = :id
), for which in 5 the annotation’s clause was not applied.
This is consistent with the purpose of the annotation, which is to always disregard records not respecting the specified condition.