hibernate-orm/migration-guide.adoc

284 lines
12 KiB
Plaintext

= 7.0 Migration Guide
:toc:
:toclevels: 4
:docsBase: https://docs.jboss.org/hibernate/orm
:versionDocBase: {docsBase}/7.0
:userGuideBase: {versionDocBase}/userguide/html_single/Hibernate_User_Guide.html
:javadocsBase: {versionDocBase}/javadocs
This guide discusses migration to Hibernate ORM version 7.0. For migration from
earlier versions, see any other pertinent migration guides as well.
[[jpa-32]]
== Jakarta Persistence 3.2
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 new `Order#getNullPrecedence()` returning `Nulls`
colliding with Hibernate's `SqmSortSpecification#getNullPrecedence` returning `NullPrecedence`. Hibernate's form
was renamed to `SqmSortSpecification#getHibernateNullPrecedence` to avoid the collision.
** `SchemaManager` is now also a JPA contract exposed as `EntityManagerFactory#getSchemaManager` which leads to type issues for
Hibernate's `SessionFactory#getSchemaManager`. Hibernate's `SchemaManager` now extends the new JPA `SchemaManager`.
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 uses `Integer` whereas Hibernate has historically used `int`. Note that this raises the possibility of a `NullPointerException` during migration if, e.g., performing direct comparisons on the timeout value against an in (auto unboxing).
See this https://in.relation.to/2024/04/01/jakarta-persistence-3/[blog post] for a good discussion of the changes in Jakarta Persistence 3.2.
[[hibernate-models]]
== Hibernate Models
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 https://github.com/hibernate/hibernate-models[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.
[[model-validation]]
== Domain Model Validations
7.0 adds many more checks about illegal use of annotations.
[[PersistentAttributeType]]
=== PersistentAttributeType
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 -
[source,java]
----
@Basic
@ManyToOne
private Employee manager;
----
or
[source,java]
----
@Lob
@ManyToOne
private Employee manager;
----
[[misplaced-annotations]]
=== Misplaced Annotations
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.
[source,java]
----
@Entity
class Book {
// defines FIELD access-type
@Id
Integer id;
// previously ignored, this is an error now
@Column(name="category")
String getType() { ... }
}
----
[[java-beans]]
=== JavaBean Conventions
Previous versions allowed some questionable (at best) attribute naming patterns. These are no longer supported. E.g.
[source,java]
----
@Basic
String isDefault();
----
[[proxy-annotation]]
== Replace @Proxy
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`, use `Session#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`).
[[flush-persist]]
== Session flush and persist
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
[source,java]
----
@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:
[source,java]
----
Parent parent = session.get( Parent.class, parentId );
parent.addChild( c1 );
----
Instead, `c1` must first be re-associated with the Session using merge:
[source,java]
----
Parent parent = session.get( Parent.class, parentId );
Child merged = session.merge( c1 );
parent.addChild( merged );
----
[[auto-cascade-persist]]
== Cascading persistence for `@Id` and `@MapsId` fields
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.
[[enum-checks]]
== Enums and Check Constraints
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.
[[ddl-implicit-datatype-timestamp]]
== Default precision for timestamp on some databases
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.
[[sf-name]]
== SessionFactory Name (and JNDI)
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.
[[hbm-transform]]
== hbm.xml Transformation
Previous versions of Hibernate performed transformations of `hbm.xml` files (with `hibernate.transform_hbm_xml.enabled=true`)
one file at a time. This is now done across the entire set of `hbm.xml` files at once.
While most users will never see this change, it might impact integrations which tie-in to
XML processing.
[[cleanup]]
== Cleanup
* Removed `SqmQualifiedJoin`. All joins are qualified.
* Removed `AdditionalJaxbMappingProducer`, deprecated in favor of `AdditionalMappingContributor`
* Removed `MetadataContributor`, deprecated in favor of `AdditionalMappingContributor`
* Removed `@Persister`.
* Removed `hibernate.mapping.precedence` and friends
* Removed `org.hibernate.Session#save` in favor of `org.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 of `org.hibernate.Session.merge`
* Removed `org.hibernate.annotations.CascadeType.SAVE_UPDATE` in favor of `org.hibernate.annotations.CascadeType.PERSIST` + `org.hibernate.annotations.CascadeType.MERGE`
* Removed `@SelectBeforeUpdate`
* Removed `org.hibernate.Session#delete` in favor of `org.hibernate.Session#remove`
* Removed `org.hibernate.annotations.CascadeType.DELETE` in favor of `org.hibernate.annotations.CascadeType#REMOVE`
* Removed the attribute value from `@DynamicInsert` and `@DynamicUpdate`
* Removed `org.hibernate.integrator.spi.Integrator#integrate(Metadata,SessionFactoryImplementor,SessionFactoryServiceRegistry)` in favor of `org.hibernate.integrator.spi.Integrator#integrate(Metadata,BootstrapContext,SessionFactoryImplementor)`
* Removed `org.hibernate.Interceptor#onLoad(Object, Serializable, Object[] , String[] , Type[] )` in favour of `org.hibernate.Interceptor#onLoad(Object, Object, Object[], String[], Type[] )`
* Removed `org.hibernate.Interceptor#onFlushDirty(Object, Serializable, Object[] , Object[], String[] , Type[] )` in favour of `org.hibernate.Interceptor#onLoad(Object, Object, Object[], Object[], String[] , Type[] )`
* Removed `org.hibernate.Interceptor#onSave(Object, Serializable, Object[], String[], Type[])` in favour of `org.hibernate.Interceptor#onSave(Object, Object, Object[], String[], Type[])`
* Removed `org.hibernate.Interceptor#onDelete(Object, Serializable, Object[], String[], Type[])` in favour of `org.hibernate.Interceptor#onDelete(Object, Serializable, Object[], String[], Type[])`
* Removed `org.hibernate.Interceptor#onCollectionRecreate(Object, Serializable)` in favour of `org.hibernate.Interceptor#onCollectionRecreate(Object, Object)`
* Removed `org.hibernate.Interceptor#onCollectionRemove(Object, Serializable)` in favour of `org.hibernate.Interceptor#onCollectionRemove(Object, Object)`
* Removed `org.hibernate.Interceptor#onCollectionUpdate(Object, Serializable)` in favour of `org.hibernate.Interceptor#onCollectionUpdate(Object, Object)`
* Removed `org.hibernate.Interceptor#findDirty(Object, Serializable, Object[], Object[], String[], Type[])` in favour of `org.hibernate.Interceptor#findDirty(Object, Object, Object[], Object[], String[], Type[])`
* Removed `org.hibernate.Interceptor#getEntity(String, Serializable)` in favour of `org.hibernate.Interceptor#getEntity(String, Serializable)`
[[todo]]
== Todos (dev)
* Look for `todo (jpa 3.2)` comments
* Look for `todo (7.0)` comments