- Apache License
- Requirements
- New Features
- Changes to API
- Changes to SPI
- Changes in Behavior
- Domain Model Validations
- StatelessSession Behavior
- Query with Implicit SELECT and No Explicit Result Type
- Session flush and persist
- Refreshing/Locking Detached Entities
- Cascading Persistence for @Id and @MapsId Attributes
- Temporal Types Returned by Native Queries
- XML FormatMapper Changes
- Criteria API and Implicit Treats
- SessionFactory Name (and JNDI)
- @OrderColumn in Unowned @OneToMany Associations
- ValidationMode#AUTO
- Changes Affecting DDL
- Connection Pools
This guide discusses migration to Hibernate ORM version 7.0. For migration from earlier versions, see any other pertinent migration guides as well.
Starting with 7.0, Hibernate ORM will be licensed under the Apache License 2.0.
Note
|
While the Hibernate team still believes that our long published interpretation of LGPL to be compatible with ASL and similar licenses, enough FUD has been spread about it to be considered a lost cause in terms of inclusion in various projects. At this point, it just makes sense to change - it opens up wider acceptance amongst Apache/Jakarta projects and others. |
Details can be seen at https://hibernate.atlassian.net/browse/HHH-19145.
As part of this effort, the Hibernate team reached out to the authors of "non-trivial" contributions to request permission to relicense their work under the Apache License. The response was overwhelming positive, although we never heard back from some contributors and another explicitly disagreed. This required a few actions on our part:
-
Dropping
hibernate-ucp
- see https://hibernate.atlassian.net/browse/HHH-19162 -
Dropping
TeradataDialect
- see https://hibernate.atlassian.net/browse/HHH-19057
-
Java 17, or greater
7.0 migrates to Jakarta Persistence 3.2 which is fairly disruptive, mainly around:
-
Type parameters:
-
Affects much of the Criteria API - especially roots, joins, paths
-
Affects much of the Entity Graph API - see org.hibernate.graph Package for details.
-
-
New JPA features colliding with previous Hibernate extension features:
-
Nulls
(JPA) v.NullPrecedence
(Hibernate), including JPA’s newOrder#getNullPrecedence()
returningNulls
colliding with Hibernate’sSqmSortSpecification#getNullPrecedence
returningNullPrecedence
. Hibernate’s form was renamed toSqmSortSpecification#getHibernateNullPrecedence
to avoid the collision. -
SchemaManager
is now also a JPA contract exposed asEntityManagerFactory#getSchemaManager
which leads to type issues for Hibernate’sSessionFactory#getSchemaManager
. Hibernate’sSchemaManager
now extends the new JPASchemaManager
. But that is a bytecode incompatibility. -
JPA has added support in its Graph API for things Hibernate has supported for some time. Some of those are collisions requiring changes to the Hibernate API.
-
Transaction#getTimeout
. JPA 3.2 adds#getTimeout
but usesInteger
whereas Hibernate has historically usedint
. Note that this raises the possibility of aNullPointerException
during migration if, e.g., performing direct comparisons on the timeout value against an in (auto unboxing).
-
See this blog post for a good discussion of the changes in Jakarta Persistence 3.2.
-
TCK Results with Java 17
-
TCK Results with Java 21
For many years Hibernate has used the Hibernate Commons Annotations (HCANN) library for handling various low-level tasks related to understanding the structure of an application domain model, reading annotations and weaving in XML mapping documents.
However, HCANN suffers from a number of limitations that continued to be problematic. And given the use of HCANN across multiple projects, doing the needed refactoring was simply not possible.
The Hibernate Models project was developed to be a better alternative
to HCANN. Hibernate Models is essentially an abstraction over reflection (Type
, Class
, Member
, …) and
annotations. Check out its project page for complete details.
7.0 uses Hibernate Models in place of HCANN.
See the website for the list of new features in the 7.0 series.
This section describes changes to contracts (classes, interfaces, methods, etc.) which are consider API.
A general theme in 7.0 has been to remove Hibernate-specific features that have a direct replacement in JPA.
Session#load
methods have been removed in favor of Session#getReference
which have the same semantic.
Session#get
methods were deprecated in favor of the JPA-standard Session#find
, and new overloads of Session#find
were added.
Note
|
Session#get was not previously deprecated as Session#load was, so it was not appropriate to remove it.
|
The forms of Session#refresh
accepting an entity-name have been removed; the passed entity already indicates the entity-name (even with dynamic models).
Session#refresh(String entityName, Object object)
-
Removed in favor of
Session#refresh(Object object)
Session#refresh(String entityName, Object object, LockOptions lockOptions)
-
Removed in favor of
Session#refresh(Object object, LockOptions lockOptions)
All forms of Session#save
, Session#update
, Session#saveOrUpdate
have been removed. See the discussion at Session flush and persist.
Session#save
-
Removed in favor of
Session#persist
. Session#update
-
Removed in favor of
Session#merge
Session#saveOrUpdate
-
Removed in favor
#persist
if the entity is transient or#merge
if the entity is detached
Relatedly, org.hibernate.annotations.CascadeType#SAVE_UPDATE
has been removed in favor of org.hibernate.annotations.CascadeType#PERSIST
and/or org.hibernate.annotations.CascadeType#MERGE
[1]
Session#delete
methods has been removed in favor of Session#remove
. Relatedly, org.hibernate.annotations.CascadeType#DELETE
was removed in favor of org.hibernate.annotations.CascadeType#REMOVE
[1]
The EntityGraph
API was enhanced in JPA 3.2, and made much more useful.
The incubating package org.hibernate.graph
contains extensions to that API, which have been significantly impacted by the migration to JPA 3.2, and by the addition of new functionality.
Furthermore, some legacy operations were declared with incorrect generic type signatures (by both JPA, and by Hibernate).
This package has been significantly re-engineered, and the impact of this effort includes:
-
some breaking changes to type signatures, and
-
a number of deprecations of legacy operations which are now covered by JPA.
Also, a key subgraph now always refers to a Map
key, and never to an entity id.
We encourage migration to the use of the new JPA-standard operations.
Or, alternatively, when building graphs, consider Hibernate’s support for textual graph parsing. See also [NamedEntityGraph].
-
Removed
@Persister
-
Removed
@Proxy
- see Replace @Proxy -
Removed
@SelectBeforeUpdate
- see Session flush and persist -
Removed
@DynamicInsert#value
and@DynamicUpdate#value
- usage indicates true -
Removed
@Loader
-
Removed
@Table
→ use JPA@Table
-
Removed
@Where
and@WhereJoinTable
→ use@SQLRestriction
or@SQLJoinTableRestriction
-
Removed
@OrderBy
→ use@SQLOrder
or JPA@OrderBy
-
Removed
@ForeignKey
→ use JPA@ForeignKey
-
Removed
@Index
→ use JPA@Index
-
Removed
@IndexColumn
→ use JPA@OrderColumn
-
Removed
@GeneratorType
(andGenerationTime
, etc) -
Removed
@LazyToOne
-
Removed
@LazyCollection
-
Replaced uses of
CacheModeType
withCacheMode
-
Removed
@Cache#include
→ use@Cache#includeLazy
-
Removed
@TestForIssue
(for testing purposes) → useorg.hibernate.testing.orm.junit.JiraKey
ororg.hibernate.testing.orm.junit.JiraKeyGroup
Applications will need to replace usages of the removed @Proxy
annotation.
@Proxy#proxyClass
has no direct replacement, but was also never needed/useful.
Here we focus on @Proxy#lazy
attribute which, again, was hardly ever useful.
By default (true), Hibernate would proxy an entity when possible and when asked for.
"Asked for" includes calls to Session#getReference
and lazy associations.
All such cases though are already controllable by the application.
-
Instead of
Session#getReference
, useSession#find
-
Use eager association fetching, for example,
-
FetchType.EAGER
(the default for to-one associations anyway), possibly combined with@Fetch
, -
EntityGraph
, or a -
@FetchProfile
.
-
The effect can also often be mitigated using Hibernate’s bytecode-based laziness (possibly combined with @ConcreteProxy
).
Hibernate now does a better and more consistent job of interpreting database-specific error codes in JDBCException
s and translating to subtypes of PersistenceException
.
In particular, interpretation of integrity constraint violations was improved significantly.
Also, LockAcquisitionException
now extends PessimisticLockException
.
-
Removed
org.hibernate.Metamodel
in favor oforg.hibernate.metamodel.model.domain.JpaMetamodel
-
Removed
SqmQualifiedJoin
- all joins are qualified. -
Both
NaturalIdLoadAccess#using(Map)
andNaturalIdMultiLoadAccess#compoundValue()
have been removed in favor ofMap#of()
-
Removed
Session.LockRequest
- useLockOptions
instead -
SessionFactory.createEntityManager()
now returnsSession
for convenience -
CommonQueryContract.setFlushMode()
was deprecated in favor ofsetQueryFlushMode
accepting aQueryFlushMode
This section describes changes to contracts (classes, interfaces, methods, etc.) which are consider SPI.
The signature of the Configurable#configure
method changed from accepting just a ServiceRegistry
instance to the new GeneratorCreationContext
interface, which exposes a lot more useful information when configuring the generator itself. The old signature has been deprecated for removal, so you should migrate any custom Configurable
generator implementation to the new one. Or better yet, consider migrating to @IdGeneratorType
.
The previously deprecated method org.hibernate.integrator.spi.Integrator#integrate(Metadata,SessionFactoryImplementor,SessionFactoryServiceRegistry)
have been removed in favor of its replacement org.hibernate.integrator.spi.Integrator#integrate(Metadata,BootstrapContext,SessionFactoryImplementor)
Quite a few (again, previously deprecated) methods on Interceptor
have been removed in favor of their replacement. This mainly deals with the change in expected Java type of identifiers (done in 6.0) from Serializable
to Object
.
-
Interceptor#onLoad
-
Interceptor#onFlushDirty
-
Interceptor#onSave
-
Interceptor#onDelete
-
Interceptor#onCollectionRecreate
-
Interceptor#onCollectionRemove
-
Interceptor#onCollectionUpdate
-
Interceptor#findDirty
-
Interceptor#getEntity
Additionally, EmptyInterceptor
was removed. As org.hibernate.Interceptor
now uses default methods, one can simply implement Interceptor
to the same end.
The API interfaces UserType
and CompositeUserType
leaked the SPI types SharedSessionContractImplementor
and SessionFactoryImplementor
, which was a layer-breaker.
The solution was to change the signature of nullSafeSet()
and nullSafeGet()
in UserType
via deprecation of the previous declarations, and remove some unnecessary parameters from methods of the incubating interface CompositeUserType
.
The types EventMonitor
and DiagonosticEvent
replace the now-deprecated SPIs EventManager
and HibernateMonitoringEvent
use for integration with Java Flight Recorder.
Hibernate now reports many more kinds of DiagnosticEvent
to JFR.
-
org.hibernate.metamodel.spi.MetamodelImplementor
was removed in favor oforg.hibernate.metamodel.MappingMetmodel
ororg.hibernate.metamodel.model.domain.JpaMetamodel
-
Removed
AdditionalJaxbMappingProducer
in favor ofAdditionalMappingContributor
. -
Removed
MetadataContributor
in favor ofAdditionalMappingContributor
7.0 adds many more checks about illegal use of annotations.
As of 7.0, Hibernate applies much better validation of an attribute specifying multiple PersistentAttributeTypes. Jakarta Persistence 3.2 has clarified this in the specification. E.g., the following examples are all now illegal -
@Basic
@ManyToOne
private Employee manager;
or
@Lob
@ManyToOne
private Employee manager;
7.0 does much more in-depth checking that annotations appear in the proper place. While previous versions did not necessarily throw errors, in most cases these annotations were simply ignored.
For example, this code now results in an error:
@Entity
class Book {
// specifies FIELD access, properties should not be annotated
@Id
Integer id;
// previously ignored, this is an error now
@Column(name="category")
String getType() { ... }
}
Starting in 7.0 it is no longer valid to combine GenerationType#SEQUENCE
with anything other than
@SequenceGenerator
nor GenerationType#TABLE
with anything other than @TableGenerator
. Previous
versions did not validate this particularly well.
Previous versions allowed some questionable (at best) attribute naming patterns. For example, this property declaration is no longer allowed:
@Basic
String isDefault();
JPA AttributeConverter
s are incompatible with the annotations @Id
, @Version
, @Enumerated
, @Embedded
, @Temporal
and all association-mapping annotations.
Previously, any converter applied to an attribute with an incompatible annotation was simply ignored.
Hibernate now reports an error in this situation.
This includes auto-applied converters.
To suppress the error for an auto-applied converter, use @Convert(disableConversion=true)
.
The behavior of Hibernate’s StatelessSession
has changed in 2 specific ways to be aware of:
A stateless session now makes use of the second-level cache by default. This will affect migrating applications using second-level cache and StatelessSession
.
To completely bypass the second-level cache, recovering the previous behavior, call setCacheMode(CacheMode.IGNORE)
.
It’s often important to explicitly disable puts to the second-level cache in code which performs bulk processing.
Set the cache mode to GET
or configure jakarta.persistence.cache.storeMode
to BYPASS
.
The configuration property hibernate.jdbc.batch_size
now has no effect on a StatelessSession.
JDBC batching may be enabled by explicitly calling setJdbcBatchSize()
.
However, the preferred approach is to use the new explicit batch operations via insertMultiple()
, updateMultiple()
, or deleteMultiple()
.
In previous versions, Hibernate allowed a query with no select
list to be passed to the overload of createQuery()
with no explicit result type parameter, for example:
List query =
session.createQuery("from X, Y")
.getResultList()
or:
List query =
session.createQuery("from X join y")
.getResultList()
The select list was inferred based on the from
clause.
In Hibernate 6 we decided to deprecate this overload of createQuery()
, since:
-
it returns a raw type
Query
, resulting in compiler warnings in client code, -
each query result must be explicitly cast from
Object
to the query result type, and -
the second query is truly ambiguous, with no obviously intuitive interpretation.
As of Hibernate 7, the method remains deprecated, and potentially-ambiguous queries are no longer accepted. Migration paths include:
-
explicitly specify the
select
list, -
add
X.class
orObject[].class
as a second argument, to disambiguate the interpretation of the query, or -
in the case where the query should return exactly one entity, explicitly assign the alias
this
to that entity.
For example, the queries above may be migrated via:
List<Object[]> result =
session.createQuery("from X, Y", Object[].class)
.getResultList()
or:
List<X> result =
session.createQuery("from X join y", X.class)
.getResultList()
The removal of CascadeType.SAVE_UPDATE
slightly changes the persist and flush behaviour to conform with the Jakarta Persistence specification.
Making a transient entity persistent or flushing a managed entity now results in an jakarta.persistence.EntityExistsException
if:
-
the entity has an association with
cascade = CascadeType.ALL
orcascade = CascadeType.PERSIST
, and -
the association references a detached instance of the associated entity class.
To avoid this exception, the reference to the detached instance should be replaced with a reference to a managed instance associated with the current session.
Such a reference may be obtained by calling merge()
or getReference()
on the detached entity instance.
Consider the following model
@Entity
class Parent {
...
@OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true)
@LazyCollection(value = LazyCollectionOption.EXTRA)
private Set<Child> children = new HashSet<>();
public void addChild(Child child) {
children.add( child );
child.setParent( this );
}
}
@Entity
class Child {
...
@ManyToOne
private Parent parent;
}
Assuming we have c1
as a detached Child
, the following code will now result in jakarta.persistence.EntityExistsException
being thrown at flush time:
Parent parent = session.find( Parent.class, parentId );
parent.addChild( c1 );
Instead, c1
must first be re-associated with the Session using merge:
Parent parent = session.find( Parent.class, parentId );
Child merged = session.merge( c1 );
parent.addChild( merged );
Traditionally, Hibernate allowed detached entities to be refreshed. However, Jakarta Persistence prohibits this practice and specifies that an IllegalArgumentException
should be thrown instead. Hibernate now fully aligns with the JPA specification in this regard.
Along the same line of thought, also acquiring a lock on a detached entity is no longer allowed.
To this effect the hibernate.allow_refresh_detached_entity
, which allowed Hibernate’s legacy refresh behaviour to be invoked, has been removed.
Previously Hibernate automatically enabled cascade=PERSIST
for association fields annotated @Id
or @MapsId
.
This was undocumented and unexpected behavior, and arguably against the intent of the Persistence specification.
Existing code which relies on this behavior should be modified by addition of explicit cascade=PERSIST
to the association field.
In the absence of a @SqlResultSetMapping
, previous versions of Hibernate used java.sql
types (Date
, Time
, Timestamp
) to represent date/time types returned by a native query.
In 7.0, such queries return types defined by java.time
(LocalDate
, LocalTime
, LocalDateTime
) by default.
The previous behavior may be recovered by setting hibernate.query.native.prefer_jdbc_datetime_types
to true
.
Previous versions of Hibernate ORM used an undefined/provider-specific format for serialization/deserialization of collections, maps and byte arrays to/from XML, which was not portable.
XML FormatMapper
implementations now use a portable format for collections, maps, and byte arrays.
This change is necessary to allow mapping basic arrays as SqlTypes.XML_ARRAY
.
The migration requires to read data and re-save it.
To retain backwards compatibility, configure the setting hibernate.type.xml_format_mapper.legacy_format
to true
.
It was previously possible to use the string version of the jakarta.persistence.criteria.Path#get
and jakarta.persistence.criteria.From#join
methods with names of attributes defined in an inheritance subtype of the type represented by the path expression. This was handled internally by implicitly treating the path as the subtype which defines said attribute. Since Hibernate 7.0, aligning with the JPA specification, the Criteria API will no longer allow retrieving subtype attributes this way, and it’s going to require an explicit jakarta.persistence.criteria.CriteriaBuilder#treat
to be called on the path first to downcast it to the subtype which defines the attribute.
Implicit treats are still going to be applied when an HQL query dereferences a path belonging to an inheritance subtype.
Hibernate defines SessionFactory#getName
(specified via cfg.xml
or hibernate.session_factory_name
) which is used to
help with (de)serializing a SessionFactory
. It is also, unless hibernate.session_factory_name_is_jndi
is set to false
,
used in biding the SessionFactory
into JNDI.
This SessionFactory#getName
method pre-dates Jakarta Persistence (and JPA). It now implements EntityManagerFactory#getName
inherited from Jakarta Persistence, which states that this name should come from the persistence-unit name.
To align with Jakarta Persistence (the 3.2 TCK tests this), Hibernate now considers the persistence-unit name if no
hibernate.session_factory_name
is specified.
However, because hibernate.session_factory_name
is also a trigger to attempt to bind the SessionFactory into JNDI,
this change to consider persistence-unit name, means that each SessionFactory
created through Jakarta Persistence now
has a name and Hibernate attempts to bind it to JNDI.
To work around this we have introduced a new hibernate.session_factory_jndi_name
setting that can be used to explicitly
specify a name for JNDI binding. The new behavior is as follows (assuming hibernate.session_factory_name_is_jndi
is not explicitly configured):
-
If
hibernate.session_factory_jndi_name
is specified, the name is used to bind into JNDI -
If
hibernate.session_factory_name
is specified, the name is used to bind into JNDI
Hibernate can use the persistence-unit name for binding into JNDI as well, but hibernate.session_factory_name_is_jndi
must be explicitly set to true.
In an unowned (mappedBy
) one-to-many association, an @OrderColumn
should, in principle, also be mapped by a field of the associated entity, and the value of the order column should be determined by the value of this field, not by the position in the list.
Previously, since version 4.1, Hibernate would issue superfluous SQL UPDATE
statements to set the value of the order column based on the state of the unowned collection.
This was incorrect according to the JPA specification, and inconsistent with the natural semantics of Hibernate.
In Hibernate 7, these SQL UPDATE
statements only occur if the @OrderColumn
is not also mapped by a field of the entity.
This section describes changes which may affect the application’s database schema.
The default precision for Oracle timestamps was changed to 9, i.e. nanosecond precision. The default precision for SQL Server timestamps was changed to 7, i.e. 100 nanosecond precision.
Note that these changes only affect DDL generation.
On DB2, SAP HANA, SQL Server and Sybase ASE, basic arrays now map to the SqlTypes.XML_ARRAY
type code,
whereas previously, the dialect mapped arrays to SqlTypes.VARBINARY
.
The SqlTypes.XML_ARRAY
type uses the xml
DDL type which enables using arrays in other features through the various XML functions.
The migration requires to read data and re-save it. Note that XML support on Sybase ASE is not enabled by default
and requires to run sp_configure 'enable xml', 1
.
To retain backwards compatibility, configure the setting hibernate.type.preferred_array_jdbc_type
to VARBINARY
.
On MySQL and MariaDB, basic arrays now map to the SqlTypes.JSON_ARRAY
type code,
whereas previously, the dialect mapped arrays to SqlTypes.VARBINARY
.
The SqlTypes.JSON_ARRAY
type uses the json
DDL type which enables using arrays in other features through the various JSON functions.
The migration requires to read data and re-save it.
To retain backwards compatibility, configure the setting hibernate.type.preferred_array_jdbc_type
to VARBINARY
.
Previously, char
and Character
fields were, by default, mapped to char(1)
columns by the schema export tool.
However, MySQL treats a char(1)
containing a single space as an empty string, resulting in broken behavior for some HQL and SQL functions.
Now, varchar(1)
is used by default.
We have decided to drop built-in support for the Vibur, Proxool and UCP Connection Pools for a variety of reasons - the main one being that we are not able to properly test them.