Skip to content

Latest commit

 

History

History
430 lines (306 loc) · 19.2 KB

migration-guide.adoc

File metadata and controls

430 lines (306 loc) · 19.2 KB

7.0 Migration Guide

This guide discusses migration to Hibernate ORM version 7.0. For migration from earlier versions, see any other pertinent migration guides as well.

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 blog post for a good discussion of the changes in Jakarta Persistence 3.2.

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 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.

Domain Model Validations

7.0 adds many more checks about illegal use of annotations.

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 -

@Basic
@ManyToOne
private Employee manager;

or

@Lob
@ManyToOne
private Employee manager;

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.

@Entity
class Book {
	// defines FIELD access-type
    @Id
    Integer id;

	// previously ignored, this is an error now
    @Column(name="category")
    String getType() { ... }
}

Identifier Generators

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.

JavaBean Conventions

Previous versions allowed some questionable (at best) attribute naming patterns. These are no longer supported. E.g.

@Basic
String isDefault();

Queries with implicit select list and no explicit result type

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, resulting in compiler warnings in client code, and

  • the second query is truly ambiguous, with no obviously intuitive interpretation.

As of Hibernate 7, the method is remains deprecated, and potentially-ambiguous queries are no longer accepted. Migration paths include:

  1. explicitly specify the select list,

  2. add X.class or Object[].class as a second argument, to disambiguate the interpretation of the query, or

  3. 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()

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).

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

@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 );

Refreshing/locking detached entities

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.

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.

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.

Date and time types returned by native queries

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.

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.

Note that these changes only affect DDL generation.

Array mapping changes on DB2, SAP HANA and Sybase ASE

On DB2, SAP HANA 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.

Array mapping changes on MySQL/MariaDB

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.

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.

Configurable generators

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.

JDBC batching with StatelessSession

Automatic JDBC batching has the side effect of delaying the execution of the batched operation, and this undermines the synchronous nature of operations performed through a stateless session. In Hibernate 7, the configuration property hibernate.jdbc.batch_size now has no effect on a stateless session. Automatic batching may be enabled by explicitly calling setJdbcBatchSize(). However, the preferred approach is to explicitly batch operations via insertMultiple(), updateMultiple(), or deleteMultiple().

hbm.xml Transformation

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 hbm.xml files at once. While most users will never see this change, it might impact integrations which tie-in to XML processing.

Cleanup

  • 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 (and GenerationTime, etc)

    • Removed @LazyToOne

    • Removed @LazyCollection

    • Removed @IndexColumn

    • Replaced uses of CacheModeType with CacheMode

    • Removed @TestForIssue (for testing purposes) → use org.hibernate.testing.orm.junit.JiraKey and org.hibernate.testing.orm.junit.JiraKeyGroup

  • Classes/interfaces

    • Removed SqmQualifiedJoin (all joins are qualified)

    • Removed AdditionalJaxbMappingProducerAdditionalMappingContributor

    • Removed MetadataContributorAdditionalMappingContributor

    • Removed EmptyInterceptor → implement org.hibernate.Interceptor directly

  • Behavior

    • 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 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 org.hibernate.Session#refresh(String entityName, Object object) in favor of org.hibernate.Session#refresh(Object object)

    • Removed org.hibernate.Session#refresh(String entityName, Object object, LockOptions lockOptions) in favor of org.hibernate.Session#refresh(Object object, LockOptions lockOptions)

    • 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)

    • Removed org.hibernate.metamodel.spi.MetamodelImplementor in favor of org.hibernate.metamodela.MappingMetmodel or org.hibernate.metamodel.model.domain.JpaMetamodel

    • Removed org.hibernate.Metamodel in favor of org.hibernate.metamodel.model.domain.JpaMetamodel

  • Settings

    • Removed hibernate.mapping.precedence and friends

    • Removed hibernate.allow_refresh_detached_entity

Todos (dev)

  • Look for todo (jpa 3.2) comments

  • Look for todo (7.0) comments