- Jakarta Persistence 3.2
- Hibernate Models
- Domain Model Validations
- Replace @Proxy
- Session flush and persist
- Cascading persistence for
@Id
and@MapsId
fields - Enums and Check Constraints
- Default precision for timestamp on some databases
- SessionFactory Name (and JNDI)
- hbm.xml Transformation
- Cleanup
- Todos (dev)
This guide discusses migration to Hibernate ORM version 7.0. For migration from earlier versions, see any other pertinent migration guides as well.
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 Graph API -
-
org.hibernate.graph.Graph.addAttributeNode(java.lang.String) defines a return while
jakarta.persistence.Graph.addAttributeNode(java.lang.String)
does not.
-
-
-
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.
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.
Note
|
Currently, the hibernate-envers module still uses HCANN. That will change during continued 7.x development.
|
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. E.g.
@Entity
class Book {
// defines FIELD access-type
@Id
Integer id;
// previously ignored, this is an error now
@Column(name="category")
String getType() { ... }
}
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#laxy
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 associations, using
-
FetchType.EAGER
(the default for to-one associations anyway), possibly combined with@Fetch
-
EntityGraph
-
@FetchProfiles
-
The effect can also often be mitigated using Hibernate’s bytecode-based laziness (possibly combined with @ConcreteProxy
).
The removal of CascadeType.SAVE_UPDATE
slightly changes the persist and flush behaviour to conform with Jakarta Persistence.
Persisting a transient entity or flushing a manged entity with an associated detached entity having the association annotated with cascade = CascadeType.ALL
or cascade = CascadeType.PERSIST
throws now an jakarta.persistence.EntityExistsException
if the detached entity has not been re-associated with the Session.
To re-associate the detached entity with the Session the Session#merge
method can be used.
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.get( Parent.class, parentId );
parent.addChild( c1 );
Instead, c1
must first be re-associated with the Session using merge:
Parent parent = session.get( Parent.class, parentId );
Child merged = session.merge( c1 );
parent.addChild( merged );
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.
Hibernate previously added support for generating check constraints for enums mapped using @Enumerated
as part of schema generation. 7.0 adds the same capability for enums mapped using an AttributeConverter
,
by asking the converter to convert all the enum constants on start up.
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.
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
have a name and Hibernate attempted to bind these 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.
Hibernate’s legacy hbm.xml
mapping schema has been deprecated for quite some time, replaced by a new mapping.xml
schema. In 7.0, this mapping.xml
is stabilized and we now offer a transformation of hbm.xml
files into mapping.xml
files.
This tool is available as both -
-
build-time transformation (currently only offered as a Gradle plugin)
-
run-time transformation, using
hibernate.transform_hbm_xml.enabled=true
Build-time transformation is preferred.
Note
|
Initial versions of the transformation processed one file at a time.
This is now done across the entire set of |
-
Annotations
-
Removed
@Persister
-
Removed
@Proxy
- see Replace @Proxy -
Removed
@SelectBeforeUpdate
-
Removed
@DynamicInsert#value
and@DynamicUpdate#value
-
Removed
@Loader
-
Removed
@Table
-
Removed
@Where
and@WhereJoinTable
-
Removed
@ForeignKey
-
Removed
@Index
-
Removed
@IndexColumn
-
Removed
@GeneratorType
(andGenerationTime
, etc) -
Removed
@LazyToOne
-
Removed
@LazyCollection
-
Removed
@IndexColumn
-
Replaced uses of
CacheModeType
withCacheMode
-
-
Classes/interfaces
-
Removed
SqmQualifiedJoin
(all joins are qualified) -
Removed
AdditionalJaxbMappingProducer
→AdditionalMappingContributor
-
Removed
MetadataContributor
→AdditionalMappingContributor
-
-
Behavior
-
Removed
org.hibernate.Session#save
in favor oforg.hibernate.Session#persist
-
Removed
org.hibernate.Session#saveOrUpdate
in favor#persist
if the entity is transient or#merge
if the entity is detached. -
Removed
org.hibernate.Session#update
in favor oforg.hibernate.Session.merge
-
Removed
org.hibernate.annotations.CascadeType.SAVE_UPDATE
in favor oforg.hibernate.annotations.CascadeType.PERSIST
+org.hibernate.annotations.CascadeType.MERGE
-
Removed
org.hibernate.Session#delete
in favor oforg.hibernate.Session#remove
-
Removed
org.hibernate.annotations.CascadeType.DELETE
in favor oforg.hibernate.annotations.CascadeType#REMOVE
-
Removed
org.hibernate.Session#refresh(String entityName, Object object)
in favor oforg.hibernate.Session#refresh(Object object)
-
Removed
org.hibernate.Session#refresh(String entityName, Object object, LockOptions lockOptions)
in favor oforg.hibernate.Session#refresh(Object object, LockOptions lockOptions)
-
Removed
org.hibernate.integrator.spi.Integrator#integrate(Metadata,SessionFactoryImplementor,SessionFactoryServiceRegistry)
in favor oforg.hibernate.integrator.spi.Integrator#integrate(Metadata,BootstrapContext,SessionFactoryImplementor)
-
Removed
org.hibernate.Interceptor#onLoad(Object, Serializable, Object[] , String[] , Type[] )
in favour oforg.hibernate.Interceptor#onLoad(Object, Object, Object[], String[], Type[] )
-
Removed
org.hibernate.Interceptor#onFlushDirty(Object, Serializable, Object[] , Object[], String[] , Type[] )
in favour oforg.hibernate.Interceptor#onLoad(Object, Object, Object[], Object[], String[] , Type[] )
-
Removed
org.hibernate.Interceptor#onSave(Object, Serializable, Object[], String[], Type[])
in favour oforg.hibernate.Interceptor#onSave(Object, Object, Object[], String[], Type[])
-
Removed
org.hibernate.Interceptor#onDelete(Object, Serializable, Object[], String[], Type[])
in favour oforg.hibernate.Interceptor#onDelete(Object, Serializable, Object[], String[], Type[])
-
Removed
org.hibernate.Interceptor#onCollectionRecreate(Object, Serializable)
in favour oforg.hibernate.Interceptor#onCollectionRecreate(Object, Object)
-
Removed
org.hibernate.Interceptor#onCollectionRemove(Object, Serializable)
in favour oforg.hibernate.Interceptor#onCollectionRemove(Object, Object)
-
Removed
org.hibernate.Interceptor#onCollectionUpdate(Object, Serializable)
in favour oforg.hibernate.Interceptor#onCollectionUpdate(Object, Object)
-
Removed
org.hibernate.Interceptor#findDirty(Object, Serializable, Object[], Object[], String[], Type[])
in favour oforg.hibernate.Interceptor#findDirty(Object, Object, Object[], Object[], String[], Type[])
-
Removed
org.hibernate.Interceptor#getEntity(String, Serializable)
in favour oforg.hibernate.Interceptor#getEntity(String, Serializable)
-
-
Settings
-
Removed
hibernate.mapping.precedence
and friends
-