This guide discusses migration to Hibernate ORM version 8.0. For migration from earlier versions, see any other pertinent migration guides as well.
See the website for the list of requirements for the 8.0 series.
One specific requirement change that is important to call out is the move to from Jakarta Persistence version 3.2 to 4.0 which has a number of impacts, many of which are discussed below.
See this issue for more details.
See the website for the list of new features in the 8.0 series.
This section describes changes to contracts (classes, interfaces, methods, etc.) which are considered API.
Jakarta Persistence now defines a contract for "stateless" processing, named EntityAgent, similar to Hibernate’s StatelessSession.
Hibernate’s StatelessSession now implements EntityAgent.
2 methods on EntityAgent clash with methods already defined on StatelessSession -
-
insert()-EntityAgentdefines avoidreturn type, whereas Hibernate has historically returned the identifier of the inserted entity. -
fetch()-EntityAgentdefines that the fetched value be returned, whereasStatelessSessionhas historically defined avoidreturn type.
In both cases, StatelessSession has been changed to match the EntityAgent signatures.
One of the biggest changes in Jakarta Persistence 4.0 is the better modeling of "queries" into selections (TypedQuery) and mutations (its new Statement contract),
aligning closely with Hibernate’s already existing SelectionQuery and MutationQuery contracts.
At the same time, it deprecated all "operation methods" from its "raw" Query contract.
Hibernate’s Query contracts implement the Jakarta Persistence ones, so changes were needed here.
Another impact is that @NamedQuery and @NamedNativeQuery (as well as Hibernate’s variants) may no longer be used to define mutation queries -
-
@NamedQuery- ASelectionQuerydefined using HQL/JPQL -
@NamedStatement- AMutationQuerydefined using HQL/JPQL -
@NamedNativeQuery- ASelectionQuerydefined using native SQL -
@NamedNativeStatement- AMutationQuerydefined using native SQL
Historically, both Hibernate and Jakarta Persistence have defined timeouts as integer values.
Confusingly, Hibernate generally used second precision while Jakarta Persistence used millisecond precision.
Starting in 3.2, Jakarta Persistence added a new Timeout class which helps alleviate this potential confusion.
Originally this new Timeout type was not exposed on any API directly, so there was no impact from an API perspective.
However, this has changed a bit in 4.0 where Timeout is now exposed on certain APIs.
This causes a signature conflict with a few of Hibernate API methods; as part of fixing those conflicts, it was decided to change all exposures of timeout in Hibernate APIs to use Timeout as this helps clarify these confusions.
As part of its support for "entity graphs", Hibernate’s org.hibernate.graph.AttributeNode contract extends fropm the Jakarta Persistence jakarta.persistence.AttributeNode.
Starting in version 4.0, Jakarta Persistence has "fixed" the use of raw types for the AttributeNode#getSubgraphs and AttributeNode#getKeySubgraphs methods.
Hibernate has therefore needed to do the same in its org.hibernate.graph.AttributeNode.
Jakarta Persistence has changed/fixed the type signature of methods in the Criteria API which return Join and Fetch based on the String name of attributes. This can lead to compilation issues if an application uses these methods in certain ways.
The API for ProcedureCall and ProcedureOutputs has been redesigned for a number of reasons, mostly -
-
Fix a long-standing bug where multiple result mappings were not handled properly.
-
Better handling of
ProcedureOutputsto avoid casts and, even worse, often untyped casts. This is achieved by the new methods added toOutput. See link:https://docs.jboss.org/hibernate/orm/8.0/whats-new/whats-new.html#procedure-outputs-casting. -
Clean up leakage of various SPI contracts into these API contracts.
-
Align with changes in Jakarta Persistence 4.0.
This section describes changes to contracts (classes, interfaces, methods, etc.) which are considered SPI.
Many changes were needed across various SPIs to accommodate the new Graph-based ActionQueue, focused around the new
org.hibernate.action.queue.spi package which primarily defines -
-
ActionQueue -
ActionQueueFactory -
QueueType -
PlanningOptions -
graph planning/bind/decompose/meta/plan contracts
Main SPI touch points include -
-
SessionFactoryImplementor#getActionQueueFactory()exposes the configured queue factory -
org.hibernate.engine.spi.ActionQueuewas renamed toActionQueueLegacyimplementing the neworg.hibernate.action.queue.spi.ActionQueueinterface -
session/event access now points at
org.hibernate.action.queue.spi.ActionQueue -
Collection SPI additions for graph decomposition
-
PersistentCollection#getRemovedEntities -
#getAddedEntities -
#getChangeSet -
CollectionChangeSet -
SnapshotIndexed
-
-
Persister/state-management SPI hooks for graph decomposition
-
Split
org.hibernate.sql.model.MutationTargetintoGraphMutationTargetandLegacyMutationTarget -
StateManagementsupport for graph-aware collection/entity state changes -
Batchsplit-
Batchnarrowed to lifecycle only -
GroupedBatch(extendsBatch) supports legacy "grouped statement" execution -
SingleStatementBatch,StatementBinder, andBatchedResultCheckersupport single statement batching (graph)
-
The dependency on Classmate was removed, which had a minor impact on certain SPIs.
In particular, ClassmateContext was completely removed.
Raw Map types were eliminated from the signatures of methods of the class
org.hibernate.jpa.boot.spi.Bootstrap.
Hibernate uses a number of implementations of its NamedQueryMemento contract to
model "named queries". Starting in version 3.2, Jakarta Persistence added the
TypedQueryReference contract intended to serve the much the same goal.
Hibernate’s NamedQueryMemento implementations were changed to extend from
TypedQueryReference.
In version 4.0, Jakarta Persistence has added some additional methods to
TypedQueryReference which now conflict with some of Hibernate’s existing methods
requiring a rename. Notably, the addition of TypedQueryReference#getParameterTypes
caused conflict’s with Hibernate’s NamedSqmQueryMemento#getParameterTypes. To
address this conflict, we’ve renamed the NamedSqmQueryMemento method to
#getAnticipatedParameterTypes.
Jakarta Persistence clarifies that scanning is the responsibility of the
container, rather than the provider. To that effect, the scanning SPI, and
related ArchiveDescriptor SPI, have been rewritten. During "EE bootstrap",
Hibernate no longer performs scanning. Instead, the jakarta.persistence.spi.PersistenceUnitInfo
has been augmented to make it clear that the container passes in any classes
discovered during scanning.
Hibernate does still provide this SPI for use by containers - Wild Fly for example takes advantage of this.
Hibernate also uses this scanning SPI when applications use HibernatePersistenceConfiguration
bootstrapping and supply the root and/or non-root URLs.
This section describes changes in behavior that applications should be aware of.
As described in What’s New and SPI Changes, 8.0 changes how Hibernate coordinates work needed for flushing. This new coordinator can often lead to different SQL or SQL being performed in different order. The legacy strategy remains temporarily available by configuration using
hibernate.flush.queue.type=legacyA HQL query like:
select items from Order /* each query result is a collection */select elements(items) from Order /* each query result is a collection */select indices(items) from Order /* each query result is a collection */now returns a detached collection without flattening.
In previous versions of Hibernate, such queries were accepted, and due to an undocumented and unsupported behavior, resulted in an implicit join and flattening of the collection. To recover this previous undocumented behavior, the singular form of the functions may be used:
select element(items) from Order /* flatten the items collection */select indice(items) from Order /* flatten the items collection */The behavior of Hibernate’s handling for creating a ProcedureCall / StoredProcedureQuery with multiple result set mappings has been changed to fix a long-standing bug and comply with the Jakarta Persistence specification.
Hibernate now interprets the multiple mappings as one per result available from the procedure. E.g.
var call = session.createProcedureCall("the_proc",
Region.class, Initiative.class);
var outputs = call.getOutputs();
// old approach with casts still works -
List<Region> regions = ( (ResultSetOutput<Region>) outputs.getCurrent() ).getResultList();
outputs.gotToNext();
// but consider this instead -
List<Initiative> initiatives = outputs.getCurrent()
.asResultSetOutput(Initiative.class)
.getResultList();Previously, collections belonging to an entity loaded in read-only mode were not marked as read-only and could be modified. This behavior was surprising and significantly undermined the benefits of the read-only mode. A collection is now set to read-only when its owning entity is read-only, and modifications to the collection result in an error.
In Hibernate 6.x query result lists with duplicate results were handled in an
ad hoc and inconsistent way by getSingleResult() and getSingleResultOrNull().
As of Hibernate 7.3, these methods always throw when the result list contains
more than one element (which is faithful to the documented semantics of these
methods). If automatic deduplication is required, use:
query.setResultListTransformer(ResultListTransformer.uniqueResultTransformer())which collapses duplicate results according to value equality.
A final field of an entity is now treated as if it were annotated @Immutable,
that is, it is excluded from dirty checking and from SQL UPDATE statements.
In order to eliminate the possibility of "silent" changes in behavior, attempts
to modify the value of a final fields using the merge() method are rejected.
If a field of an entity can actually be updated via merge(), the field must be
declared non-final.
Jakarta Persistence now defines a series of overloaded EntityHandler.get() methods as corollaries to EntityHandler.find().
These methods are defined to throw an EntityNotFoundException rather than return null.
This operates quite differently from the older Hibernate Session.get() methods which
-
still returned null if no entity with that is was found
-
force initialized the entity, if there was one and it was previously uninitialized.
Applications which use(d) Session.get() should be aware of this change in behavior.
As discussed in Scanning above, Hibernate no longer performs scanning during "EE bootstrap". It is expected by Jakarta Persistence that the container perform the scanning prior to bootstrapping Hibernate.
Calls to EntityGraph.removeAttributeNode() and EntityGraph.removeAttributeNodes() now behave in specification compliant way -
when the graph is used as a "load graph", removing an attribute "suppresses inclusion of an attribute mapped for eager fetching".
Jakarta Persistence has clarified that listener-style callback classes (@EntityListeners) may define multiple
methods for a given event type -
An entity listener class may have multiple callback methods for a given type of lifecycle event, but at most one callback method for a given type of event and given parameter type.
Using <E> as a generic type for the signatures, it says -
where
Eis an entity class, a mapped superclass, or a supertype of the entity class or mapped superclass to which the entity listener applies. If multiple entity classes are assignable to the typeE, the callback method is invoked for any such class to which the entity listener applies.
@Entity
@EntityListeners( AnimalWatcher.class )
public class Cat implements Animal { ... }
@Entity
@EntityListeners( AnimalWatcher.class )
public class Dog implements Animal { ... }
public class AnimalWatcher {
@PostInsert
public void postInsert(Animal animal) { ... }
@PostInsert
public void postInsert(Cat animal) { ... }
@PostInsert
public void postInsert(Dog animal) { ... }
}Be aware that creating a Cat, with the above model, will result in calls to both postInsert(Animal) and postInsert(Cat); and
that creating a Dog, both postInsert(Animal) and postInsert(Dog).
This section describes changes in XML Schema Descriptors
[[xsd-comments] === Table and Column Comments
Previous versions of the mapping.xsd defined table and column comments (for schema export)
using XSD attributes. This was a decision made at the time to facilitate users migrating
from hbm.xml mapping format. In the intervening period, Jakarta Persistence has
also added comments to its table and column XSD types, but using dedicated element.
We now align with the Jakarta Persistence approach of using elements. This will require
changes any mapping.xml documents which define table or column comments. E.g.,
<entity ...>
<table ... comment="some comment"/>
</entity>would need to be changed to
<entity ...>
<table>
<comment>some comment</comment>
</table>
</entity>The SpannerPostgreSQLDialect, which was previously part of the hibernate-community-dialects artifact under the package name org.hibernate.community.dialect, has been migrated to the hibernate-core artifact and package name org.hibernate.dialect.
Users currently using the community dialect must update their dialect configuration to use org.hibernate.dialect.SpannerPostgreSQLDialect (or rely on Hibernate’s automatic dialect resolution).
This section describes changes to DDL generated by the schema export tooling. Such changes typically do not impact programs using a relational schema managed externally to Hibernate.
|
Note
|
This migration is entirely optional. Hibernate Envers continues to work as before in 8.0. However, if you wish to take advantage of the new core @Audited functionality, this section describes the steps involved.
|
The new core audit support provides very similar functionality to Envers with a simpler programming model: you can use standard Session APIs (and even HQL queries!) within a temporal session opened via atChangeset(), or the more specialized AuditLog functionality, to access the audit logs of your entities.
| Envers | Core |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Define a @Changelog entity that maps to the existing REVINFO table (or use the built-in DefaultChangelog):
@Entity
@Table(name = "REVINFO")
@Changelog(listener = MyChangesetListener.class)
public class MyRevision extends ChangelogMapping { ... }| Envers | Core |
|---|---|
|
|
|
|
|
|
|
|
|
HQL in |
|
|
Custom |
HQL with |