908 lines
34 KiB
Plaintext
908 lines
34 KiB
Plaintext
= 6.0 Migration Guide
|
|
:toc:
|
|
:toclevels: 4
|
|
:docsBase: https://docs.jboss.org/hibernate/orm/6.0
|
|
:userGuideBase: {docsBase}/userguide/html_single/Hibernate_User_Guide.html
|
|
:javadocsBase: {docsBase}/javadocs
|
|
:fn-converter: footnote:converter[Think `AttributeConverter`]
|
|
|
|
|
|
This guide discusses migration from Hibernate ORM version 6.0. For migration from
|
|
earlier versions, see any other pertinent migration guides as well.
|
|
|
|
== Java 11
|
|
|
|
With 6.0, Hibernate ORM has moved to expect Java 11 as its baseline version.
|
|
|
|
|
|
== Jakarta Persistence
|
|
|
|
6.0 moves from Java Persistence as defined by the Java EE specs to
|
|
Jakarta Persistence as defined by the Jakarta EE spec. The most immediate
|
|
impact of this change is that applications would need to be updated to use
|
|
the Jakarata Persistence classes (`jakarta.persistence.*`) instead of the Java
|
|
Persistence ones (`javax.persistence.*`).
|
|
|
|
The Jakarta spec also renames the JPA settings (again, from `javax.persistence.*` to
|
|
'jakarta.persistence.*') and defines a new set of XSD namespaces for `orm.xml` and
|
|
`persistence.xml` files.
|
|
|
|
Jakarta provides a https://github.com/eclipse/transformer[transformer]
|
|
tool which, along with appropriate "rules", will transform a project from Java Persistence to
|
|
Jakarta Persistence. This can update package names in source, settings, xsd references and more.
|
|
|
|
NOTE: As far as the XSD and setting changes, Hibernate does support both sets as a temporary aid
|
|
in migration. It logs a deprecation warning when the Java EE variants are used. See the `rules/`
|
|
directory in the project root for the configuration used to migrate Hibernate itself.
|
|
|
|
|
|
[[read-jdbc]]
|
|
== Reading from JDBC
|
|
|
|
One of the main reasons for 6.0 development was the move from reading results
|
|
from the JDBC `ResultSet` by name (read-by-name) as done in previous versions
|
|
of Hibernate, to reading the results by position (read-by-position).
|
|
|
|
Throughput testing of Hibernate showed that its use of read-by-name was its limiting factor
|
|
in any further scaling in terms of throughput - much of the issue was actually the call into
|
|
the `ResultSet`. We like to improve performance all the time :)
|
|
|
|
This change, along with <<sql>>, helped achieve this goal.
|
|
|
|
As discussed in <<type>> though, this change has a very big impact on Hibernate's mapping type system
|
|
|
|
|
|
[[sql]]
|
|
== Generated SQL
|
|
|
|
1. Column aliases are no longer generated.
|
|
2. Column references are "unique-d".
|
|
3. Better definition of joins
|
|
4. Better determination of unnecessary joins (secondary tables, inheritance tables)
|
|
|
|
[[identifier-object]]
|
|
== Identifier as Object
|
|
|
|
Previous versions of Hibernate required that all identifier types implement `Serializable`. 6.0
|
|
removes this restriction - identifiers can be any `Object`.
|
|
|
|
This change affects many api and spi methods previously defined using `Serializable`.
|
|
|
|
|
|
[[id-gen-type]]
|
|
== @IdGeneratorType
|
|
|
|
6.0 adds a new `@IdGeneratorType` annotation that allows better, type-safe way
|
|
to define custom generators to use for identifier generation.
|
|
|
|
// todo (6.0 - @Steve - need to add content about this to the User Guide
|
|
|
|
[[id-sequence-table-name]]
|
|
== Implicit Identifier Sequence and Table Name
|
|
|
|
The way in which Hibernate determines implicit names for sequences and tables associated with identifier
|
|
generation has changed in 6.0 which may affect migrating applications.
|
|
|
|
To help with backwards compatibility, or to apply any general naming strategy, 6.0 introduces the
|
|
`org.hibernate.id.enhanced.ImplicitDatabaseObjectNamingStrategy` contract which can be specified using
|
|
the `hibernate.id.db_structure_naming_strategy` setting. See discussion at
|
|
link:{javadocsBase}/org/hibernate/cfg/AvailableSettings.html#ID_DB_STRUCTURE_NAMING_STRATEGY
|
|
|
|
For backwards compatibility, use either `hibernate.id.db_structure_naming_strategy=single` or
|
|
`hibernate.id.db_structure_naming_strategy=legacy` depending on needs
|
|
|
|
|
|
[[type]]
|
|
== Type system
|
|
|
|
Another change is to generally modernize Hibernate's mapping annotations and make them
|
|
more type-safe.
|
|
|
|
We decided this is the right time since 6.0 is a major release and most of the type-related
|
|
contracts were already changing to implement the <<read-jdbc,read-by-position>> changes.
|
|
|
|
One part of this work was the removal of various String-based approaches for specifying Types to use from annotations, including
|
|
the removal of `@AnyMetaDef`, `@AnyMetaDefs`, `@TypeDef` and `@TypeDefs`, as well as
|
|
removing annotation attributes accepting the type to use as a String (e.g. `org.hibernate.annotations.CollectionType#type`)
|
|
|
|
The https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#domain-model[User Guide]
|
|
covers the details of mapping your domain model.
|
|
|
|
|
|
[[rename-java-type]]
|
|
=== Renaming of JavaTypeDescriptor contract
|
|
|
|
The interface `org.hibernate.type.descriptor.java.JavaTypeDescriptor` has been renamed to
|
|
`org.hibernate.type.descriptor.java.JavaType`
|
|
|
|
|
|
[[rename-jdbc-type]]
|
|
=== Renaming of SqlTypeDescriptor contract
|
|
|
|
The interface `org.hibernate.type.descriptor.sql.SqlTypeDescriptor` has been renamed to
|
|
`org.hibernate.type.descriptor.jdbc.JdbcType`.
|
|
|
|
|
|
[[basic-type]]
|
|
=== Basic types
|
|
|
|
Basic mappings are no longer configurable through the `BasicType` contract. Instead,
|
|
users configure the different aspects of mapping the basic value to the database -
|
|
|
|
* `JavaType`
|
|
* `JdbcType`
|
|
* `BasicValueConverter` {fn-converter}
|
|
* `MutabilityPlan`
|
|
|
|
This also made the various implementations of `BasicType` obsolete, thus they have been removed.
|
|
`NamedBasicTypeImpl` takes the role of all the previous specific implementations by wrapping a
|
|
`JdbcType` and `JavaType`.
|
|
|
|
The `StandardBasicTypes` class previously exposed `BasicType` instance fields, which now have been
|
|
replaced with fields of the type `BasicTypeReference`. APIs that previously accepted just a `BasicType`
|
|
have been adapted to also accept a `BasicTypeReference` which allows for uses of `StandardBasicType`
|
|
fields to stay mostly source compatible.
|
|
|
|
See https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#basic for details.
|
|
|
|
==== UserType
|
|
|
|
`UserType` is still supported, and is specified using the new `Type` annotation.
|
|
|
|
See https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#basic-mapping-custom for details.
|
|
|
|
|
|
==== Boolean converters
|
|
|
|
Hibernate now provides standard `AttributeConverter` implementations for handling different database representations
|
|
as boolean values in the domain model:
|
|
|
|
`YesNoConverter`:: Handles values stored in the database as either `Y` or `N`. Replaces the removed `YesNoBooleanType` (`yes_no`)
|
|
`TrueFalseConverter`:: Handles values stored in the database as either `T` or `F`. Replaces the removed `TrueFalseBooleanType` (`true_false`)
|
|
`NumericBooleanConverter`:: Handles values stored in the database as either `1` or `0`. Replaces the removed `NumericBooleanType` (`numeric_boolean`)
|
|
|
|
E.g.
|
|
|
|
```
|
|
@Type(type="yes_no")
|
|
boolean isActive;
|
|
```
|
|
|
|
becomes
|
|
|
|
```
|
|
@Convert(converter=YesNoConverter.class)
|
|
boolean isActive;
|
|
```
|
|
|
|
In fact, if your application consistently maps booleans to the same database representation you can
|
|
even register one as an auto-apply converter.
|
|
|
|
See https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#basic-boolean for details.
|
|
|
|
|
|
=== Embeddables / components
|
|
|
|
Mapping of embeddables had a few changes as well.
|
|
|
|
|
|
==== Different embeddable mappings
|
|
|
|
Multiple component mappings for the same Java class with different property mappings is no
|
|
longer supported. Every property mapping combination should have its own Java class
|
|
|
|
|
|
==== EmbeddableInstantiator
|
|
|
|
6.0 introduces the new `EmbeddableInstantiator` contract.
|
|
|
|
`EmbeddableInstantiator` supports constructor-injection! Note, however, that embeddables used as
|
|
identifiers cannot use constructor injection.
|
|
|
|
See https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#embeddable-instantiator for details.
|
|
|
|
==== CompositeUserType changes
|
|
|
|
The `CompositeUserType` interface was re-implemented to be able to model user types as proper embeddable types.
|
|
A major difference to 5.x is the introduction of an "embeddable projection" that is used to determine the mapping structure.
|
|
|
|
Previously, a `CompositeUserType` had to provide property names and types through dedicated accessor methods,
|
|
but this was complicated for non-basic mappings and required quite some knowledge about Hibernate internals.
|
|
With 6.0 these methods are replaced with a method that returns an "embeddable projection" class.
|
|
The class is like a regular `@Embeddable` class and is used to determine the mapping structure for the `CompositeUserType`.
|
|
|
|
Component values of a user type object are accessed by property index. The property index is 0-based and can be determined
|
|
by sorting the persistent attribute names lexicographically ascending and using the 0-based position as property index.
|
|
|
|
For example, the following component:
|
|
|
|
```java
|
|
public class MonetaryAmountEmbeddable {
|
|
BigDecimal value;
|
|
Currency currency;
|
|
}
|
|
```
|
|
|
|
will assign property index 0 to `currency` and index 1 to `value`.
|
|
|
|
Note that it is not possible anymore to use `@Columns` to specify the names of columns of a composite user type mapping.
|
|
Since a `CompositeUserType` now constructs a proper component, it is necessary to use the `@AttributeOverride` annotation.
|
|
|
|
=== Plural attributes
|
|
|
|
6.0 defines 2 main ways to influence collection mapping `@CollectionType` and `@CollectionTypeRegistration`
|
|
|
|
[[collection-type-ann]]
|
|
==== `@CollectionType`
|
|
|
|
The `@CollectionType` annotation is kept from 5.x. However, where it used to define
|
|
|
|
```
|
|
String type();
|
|
```
|
|
|
|
it now defines
|
|
|
|
```
|
|
Class<? extends UserCollectionType> type();
|
|
```
|
|
|
|
The type to use must be a `UserCollectionType` (can no longer be a `CollectionType`) and
|
|
it no longer works with type-definitions. See <<type>> for further discussion of general type changes.
|
|
|
|
See https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#collection-type-ann
|
|
for details of using `@CollectionType`
|
|
|
|
|
|
[[collection-type-reg-ann]]
|
|
==== `@CollectionTypeRegistration`
|
|
|
|
Allows to "auto apply" a `UserCollectionType` whenever Hibernate encounters a particular
|
|
plural attribute classification
|
|
|
|
See https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#collection-type-reg-ann
|
|
for details of using `@CollectionTypeRegistration`
|
|
|
|
=== Duration mapping changes
|
|
|
|
Duration now maps to the type code `SqlType.INTERVAL_SECOND` by default, which maps to the SQL type `interval second`
|
|
if possible, and falls back to `numeric(21)`.
|
|
In either case, schema validation errors could occur as 5.x used the type code `Types.BIGINT`.
|
|
|
|
Migration to `numeric(21)` should be easy. The migration to `interval second` might require a migration expression like
|
|
`cast(cast(old as numeric(21,9) / 1000000000) as interval second(9))`.
|
|
|
|
To retain backwards compatibility, configure the setting `hibernate.type.preferred_duration_jdbc_type` to `NUMERIC`.
|
|
|
|
=== UUID mapping changes
|
|
|
|
UUID now maps to the type code `SqlType.UUID` by default, which maps to the SQL type `uuid`
|
|
if possible, and falls back to `binary(16)`.
|
|
Due to the change to the native `uuid` type, schema validation errors could occur on database with native data type support.
|
|
|
|
The migration to `uuid` might require a migration expression like `cast(old as uuid)`.
|
|
|
|
To retain backwards compatibility, configure the setting `hibernate.type.preferred_uuid_jdbc_type` to `BINARY`.
|
|
|
|
=== Instant mapping changes
|
|
|
|
Instant now maps to the type code `SqlType.TIMESTAMP_UTC` by default, which maps to the SQL type `timestamp with time zone`
|
|
if possible, and falls back to `timestamp`.
|
|
Due to this change, schema validation errors could occur on some databases.
|
|
|
|
The migration to `timestamp with time zone` might require a migration expression like `cast(old as timestamp with time zone)`.
|
|
|
|
To retain backwards compatibility, configure the setting `hibernate.type.preferred_instant_jdbc_type` to `TIMESTAMP`.
|
|
|
|
[[query]]
|
|
== Query
|
|
|
|
Quite a few changes have been made to how Query works.
|
|
|
|
|
|
[[query-sqm]]
|
|
=== SQM (HQL/Criteria)
|
|
|
|
Another major change in 6.0 is the move to a dedicated tree structure to model
|
|
HQL and Criteria queries. This tree structure is called the Semantic Query Model, or
|
|
SQM for short.
|
|
|
|
|
|
[[query-sqm-rows]]
|
|
==== Result "rows"
|
|
|
|
Queries that use joins without specifying a select clause (e.g. `from Person p join p.address`)
|
|
used to return a `List<Object[]>`. Starting with 6.0, such a query instead returns
|
|
`List<Person>`
|
|
|
|
The HQL query `select p, a from Person p join p.address a` returns instead a `List<Object[]>`.
|
|
|
|
```
|
|
List<Person> result = session.createQuery("from Person p join p.address").list();
|
|
List<Object[]> results
|
|
```
|
|
|
|
|
|
==== Multi-table Mutation Queries
|
|
|
|
The implementations for bulk SQM DML statements like `insert`, `update` and `delete` were significantly improved in 6.0.
|
|
An important bug fix is, that `delete` statements now properly clean up collection tables.
|
|
`insert` statements now also support inserting into multi-table entities by making use of special purpose temporary tables
|
|
into which the insert goes and is then split up into the respective tables.
|
|
|
|
There are currently 2 implementation strategies:
|
|
|
|
* Using temporary tables (the default)
|
|
* Using DML in CTEs (used on DB2 and PostgreSQL)
|
|
|
|
The temporary table approach is pretty simple and works in a similar way to how 5.x already implemented it.
|
|
Data or primary key values are first inserted into a temporary table and then the DML changes are applied to the various
|
|
tables that are affected by the SQM DML statement.
|
|
|
|
The CTE approach is new and implements a more performant approach by executing a single statement,
|
|
containing the various individual DML statements that would normally be executed separately.
|
|
This allows to run SQM DML statements in a single JDBC operation that does not move any data between the database and the application,
|
|
which should provide a significant boost for statements that involve many rows.
|
|
|
|
Note that the configuration property `hibernate.hql.bulk_id_strategy` was changed to `hibernate.query.mutation_strategy`
|
|
which will now refer to classes or objects implementing `org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy`.
|
|
|
|
|
|
|
|
[[query-criteria-copy]]
|
|
==== Hibernate Criteria behavior change
|
|
|
|
By default, when bootstrapping Hibernate through the native bootstrap APIs or when explicitly disabling the newly introduced
|
|
`hibernate.criteria.copy_tree` configuration property, it is expected that criteria queries passed to
|
|
`jakarta.persistence.EntityManager#createQuery(CriteriaQuery)`, `jakarta.persistence.EntityManager#createQuery(CriteriaUpdate)`
|
|
or `jakarta.persistence.EntityManager#createQuery(CriteriaDelete)` are not mutated afterwards to avoid the need for copying the criteria query.
|
|
|
|
Prior to 6.0, mutations to criteria queries didn't affect `Query` instances created from that.
|
|
To retain backwards compatibility, enable the `hibernate.criteria.copy_tree` configuration property.
|
|
|
|
|
|
[[query-sqm-pass-thru]]
|
|
==== Pass-through tokens
|
|
|
|
The use of plain HQL identifiers in e.g. functions which couldn't be interpreted as an attribute of a `FROM` root
|
|
were passed through as-is to SQL in Hibernate 5.x which was dropped in 6.0 because we believe this is unsafe
|
|
and might lead to surprising results. HQL queries that relied on this, need to be changed and use the newly introduced
|
|
`sql` function, which allows passing through the content of a string literal to SQL.
|
|
|
|
An HQL query like `select substring( e.description, 21, 11, octets ) from AnEntity e`, which relies on this for passing through `octets`
|
|
can be migrated to `select substring( e.description, 21, 11, sql('octets') ) from AnEntity e`.
|
|
|
|
|
|
[[query-sqm-distinct]]
|
|
==== DISTINCT
|
|
|
|
Starting with Hibernate ORM 6 it is no longer necessary to use *distinct* in JPQL and HQL
|
|
to filter out the same parent entity references when join fetching a child collection.
|
|
The returning duplicates of entities are now always filtered by Hibernate.
|
|
|
|
Which means that for instance it is no longer necessary to set `QueryHints#HINT_PASS_DISTINCT_THROUGH` to `false`
|
|
in order to skip the entity duplicates without producing a `distinct` in the SQL query.
|
|
|
|
From Hibernate ORM 6, `distinct` is always passed to the SQL query and the flag `QueryHints#HINT_PASS_DISTINCT_THROUGH`
|
|
has been removed.
|
|
|
|
|
|
==== Association Comparisons
|
|
|
|
Previously Hibernate did allow comparing an association with an FK value like `... where alias.association = 1`
|
|
or `... where alias.association = alias.association.id` or even `... where alias.association = :param` where `param`
|
|
is bound to an integer `1`. This was supported prior to Hibernate 6.0 if the foreign key for the association is an integer.
|
|
|
|
The right way to do this is de-referencing the association by the FK attribute `... where alias.association.id = 1`
|
|
which is guaranteed to not produce a join, or use an entity reference for `... where alias.association = :param`
|
|
where `param` is bound to `entityManager.getReference(EntityClass.class, 1)`.
|
|
|
|
|
|
|
|
[[query-sqm-psuedo-attr]]
|
|
==== Collection psuedo-attributes
|
|
|
|
Prior to 6.0, it was possible to de-reference special properties on plural attributes like `size` which was dropped.
|
|
The special properties lead to confusion and were sometimes ambiguous. The replacement is the function syntax.
|
|
|
|
size::
|
|
The collection size can be determined by using the `size( pluralAttribute )` function instead
|
|
|
|
elements::
|
|
The collection elements can be referred to by using the `value( pluralAttribute )` function instead
|
|
|
|
indices::
|
|
The collection indices can be referred to by using the `index( pluralAttribute )` or `key( pluralAttribute )` function instead
|
|
|
|
index::
|
|
The collection index can be referred to by using the `index( pluralAttribute )` or `key( pluralAttribute )` function instead
|
|
|
|
maxindex::
|
|
The collection maximum index can be determined by using the `maxindex( pluralAttribute )` function instead
|
|
|
|
minindex::
|
|
The collection minimum index can be determined by using the `minindex( pluralAttribute )` function instead
|
|
|
|
maxelement::
|
|
The collection maximum element can be determined by using the `maxelement( pluralAttribute )` function instead
|
|
|
|
minelement::
|
|
The collection minimum element can be determined by using the `minelement( pluralAttribute )` function instead
|
|
|
|
|
|
|
|
[[query-native]]
|
|
=== NativeQuery
|
|
|
|
As `NativeQuery` extends from `Query`, all the changes listed in <<query>> also apply
|
|
to `NativeQuery`.
|
|
|
|
Some additional changes apply specifically to `NativeQuery`
|
|
|
|
|
|
[[query-ordinal-param]]
|
|
=== Ordinal Parameters binding
|
|
|
|
HQL ordinal parameter binding is 1-based, this means that queries like
|
|
|
|
```
|
|
s.createQuery( "select p from Parent p where id in ?0", Parent.class );
|
|
query.setParameter( 0, Arrays.asList( 0, 1, 2, 3 ) );
|
|
```
|
|
|
|
that uses a 0-based positional binding are not supported, and they should be changed to the following
|
|
|
|
```
|
|
s.createQuery( "select p from Parent p where id in ?`", Parent.class );
|
|
query.setParameter( 1, Arrays.asList( 0, 1, 2, 3 ) );
|
|
```
|
|
|
|
|
|
[[proc-call-param]]
|
|
=== ProcedureCall / StoredProcedureQuery Parameters
|
|
|
|
For parameters defined on a ProcedureCall as accepting binding (IN and INOUT), a distinction is now
|
|
made between whether `setParameter` is called or not. If `setParameter` was called, whatever value
|
|
was set by the user is passed to the database. If it was not called, Hibernate will not
|
|
set any value which triggers the default value defined on the database procedure argument be used
|
|
|
|
[[query-result-cache]]
|
|
=== Query result cache
|
|
|
|
Another change in 6.0 is related to the query result cache.
|
|
|
|
In previous versions, when the query-cache is enabled and a query returning entities is executed, only the entity identifiers were stored in the query-cache. If second-level caching is enabled for a returned entity, the entity data was stored in its second-level cache region.
|
|
|
|
Storing just the identifiers in the query-cache has a major drawback when fetching is defined for the query (dynamic fetch, entity-graph, etc) as it can, and often does, lead to N+1 selects.
|
|
|
|
Starting in 6.0, we now store the complete set of data for the entity into the query-cache. This also can have a drawback related to the size of caches. We plan to address this further in later 6.x releases to allow storing just the identifiers along the lines of the previous behavior.
|
|
|
|
E.g.
|
|
|
|
```
|
|
Statistics stats = sessionFacroty.getStatistics();
|
|
|
|
// First time the query is executed, query and results are cached and both the query and entity chache will be populated.
|
|
|
|
TypedQuery<Employee> query = session.createQuery( "select e from Employee e", Employee.class )
|
|
.setHint( HINT_CACHEABLE, true );
|
|
|
|
List<Employee> employees = query.getResultList();
|
|
assertEquals( 1, employees.size() );
|
|
|
|
assertEquals( 0, stats.getQueryCacheHitCount() );
|
|
assertEquals( 1, stats.getQueryCacheMissCount() );
|
|
assertEquals( 1, stats.getQueryCachePutCount() ); // query cache is populated.
|
|
|
|
assertEquals( 0, stats.getSecondLevelCacheHitCount() );
|
|
assertEquals( 0, stats.getSecondLevelCacheMissCount() );
|
|
assertEquals( 1, stats.getSecondLevelCachePutCount() ); // entity cache is populated as well.
|
|
|
|
stats.clear();
|
|
|
|
// Second time the same query is executed only the query cache will be hit.
|
|
|
|
TypedQuery<Employee> query = session.createQuery( "select e from Employee e", Employee.class )
|
|
.setHint( HINT_CACHEABLE, true );
|
|
List<Employee> employees = query.getResultList();
|
|
assertEquals( 1, employees.size() );
|
|
|
|
assertEquals( 1, stats.getQueryCacheHitCount() ); // the query cache is hit.
|
|
assertEquals( 0, stats.getQueryCacheMissCount() );
|
|
assertEquals( 0, stats.getQueryCachePutCount() );
|
|
|
|
assertEquals( 0, stats.getSecondLevelCacheHitCount() );
|
|
assertEquals( 0, stats.getSecondLevelCacheMissCount() );
|
|
assertEquals( 0, stats.getSecondLevelCachePutCount() ); // No need to hit the entity cache because the query cache contains all the entity data.
|
|
|
|
```
|
|
|
|
|
|
[[query-stream]]
|
|
=== Stream
|
|
|
|
`jakarta.persistence.Query#getResultStream()` and `org.hibernate.query.Query#stream()` no longer
|
|
return a `Stream` decorator. In order to close the underlying IO resources, it is now necessary to
|
|
explicitly call the `Stream#close()` method.
|
|
|
|
This change makes the Streams returned by Hibernate behave as defined in the JDK
|
|
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html[Stream]
|
|
documentation, which is quite explicit about the need for an explicit call to `close` by the user
|
|
to avoid resource leakages.
|
|
|
|
|
|
== Interceptor
|
|
|
|
The signature of the `#onSave` method has been changed from
|
|
```
|
|
boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types)
|
|
```
|
|
|
|
to
|
|
|
|
```
|
|
boolean onSave(Object entity, Object id, Object[] state, String[] propertyNames, Type[] types)
|
|
```
|
|
|
|
to account for the general change in expected identifier type from `Serializable` to `Object`.
|
|
See <<identifier-object>>.
|
|
|
|
If custom Interceptor implementations do not use `@Override` on their implementations, this
|
|
can lead to situations where a custom Interceptor no longer overrides this method. Moral
|
|
of the story... always use `@Override` - this is why it exists
|
|
|
|
|
|
== Fetch circularity determination
|
|
|
|
As back-ground, Hibernate does understand whether a fetch is actually, truly circular. It simply
|
|
understands that while walking a fetch-graph it encounters the same table/column(s) making up a particular
|
|
foreign-key. In this case, it simply stops walking the graph any deeper.
|
|
|
|
This
|
|
|
|
Previous versions of Hibernate determined fetches using a depth-first approach, which occasionally led
|
|
to odd "circularity" determination. Starting with 6.0, we now perform fetch determination using a width
|
|
first approach.
|
|
|
|
As back-ground, Hibernate does not always know that a fetch is truly
|
|
circular. So it uses the approach that seeing the same table and column(s) as keys might be a circularity
|
|
and stops processing fetches using that table/column(s) combination.
|
|
|
|
Given a model such as
|
|
|
|
```
|
|
@Entity
|
|
class Node {
|
|
|
|
@ManyToOne
|
|
|
|
Node node1;
|
|
|
|
@ManyToOne
|
|
Node node2;
|
|
|
|
}
|
|
```
|
|
|
|
Hibernate previously generated joins by walking the entity association graph for the `Node#node1` sub-tree prior to walking the `Node#node2` sub-tree.
|
|
Since the associations are all eager, Hibernate 6.0 now executes a query with 4 joins
|
|
|
|
```
|
|
FROM Node
|
|
JOIN Node.node1
|
|
JOIN Node.node1.node2
|
|
JOIN Node.node2
|
|
JOIN Node.node2.node1
|
|
```
|
|
|
|
whereas before it executed
|
|
|
|
```
|
|
FROM Node
|
|
JOIN Node.node1
|
|
JOIN Node.node1.node2
|
|
```
|
|
|
|
and issued a select for `Node.node2` if the FK of `Node.node2` was not null
|
|
|
|
```
|
|
FROM Node.node2
|
|
JOIN Node.node2.node1
|
|
JOIN Node.node2.node1.node2
|
|
```
|
|
|
|
In this simple example this is not such a big deal, but if the number of eager fetched self-associations
|
|
is increased to e.g. 3 like here:
|
|
|
|
```
|
|
@Entity
|
|
class Node {
|
|
|
|
@ManyToOne
|
|
Node node1;
|
|
|
|
@ManyToOne
|
|
Node node2;
|
|
|
|
@ManyToOne
|
|
Node node3;
|
|
|
|
}
|
|
```
|
|
|
|
this results in mind-blowing 15 joins
|
|
|
|
```
|
|
FROM Node
|
|
JOIN Node.node1
|
|
JOIN Node.node1.node2
|
|
JOIN Node.node1.node2.node3
|
|
JOIN Node.node1.node3
|
|
JOIN Node.node1.node3.node2
|
|
JOIN Node.node2
|
|
JOIN Node.node2.node1
|
|
JOIN Node.node2.node1.node3
|
|
JOIN Node.node2.node3
|
|
JOIN Node.node2.node3.node1
|
|
JOIN Node.node3
|
|
JOIN Node.node3.node1
|
|
JOIN Node.node3.node1.node2
|
|
JOIN Node.node3.node2
|
|
JOIN Node.node3.node2.node1
|
|
```
|
|
|
|
as you can see, this leads to a lot of joins very quickly, but the behavior of 5.x simply was not intuitive.
|
|
To avoid creating so many joins, and also in general, we recommend that you use lazy fetching i.e. `@ManyToOne(fetch = FetchType.LAZY)`
|
|
or `@OneToOne(fetch = FetchType.LAZY)` for most associations, but this is especially important if you have multiple self-referencing associations as you can see in the example.
|
|
|
|
|
|
== Restructuring of `org.hibernate.loader`
|
|
|
|
The contents of the `loader.collection` package were restructured into `loader.ast.spi` and `loader.ast.internal`
|
|
as well as adapted to the SQM API.
|
|
|
|
The contents of `loader.custom` were adapted and moved to `query.sql`.
|
|
|
|
The contents of `loader.entity` and `loader.plan` were removed
|
|
|
|
|
|
== Restructuring of the sql package
|
|
|
|
The contents of `sql.ordering` were adapted and moved to `metamodel.mapping.ordering.ast`.
|
|
|
|
Classes of the `sql` package that were previously used for building SQL, but aren't needed anymore, were removed.
|
|
The SQL generation is now fully handled through the `SqlAstTranslator` which a `Dialect` exposes a factory for.
|
|
|
|
|
|
== Deprecation of hbm.xml mappings
|
|
|
|
Legacy `hbm.xml` mapping format is considered deprecated and will no longer supported beyond 6.x.
|
|
|
|
|
|
== Association laziness now respected
|
|
|
|
Prior to Hibernate 6.0, lazy associations that used `fetch="join"` or `@Fetch(FetchMode.JOIN)` were considered eager
|
|
when loaded by-id i.e. through `Session#get`/`EntityManager#find`, even though for queries the association was treated as lazy.
|
|
|
|
Starting with Hibernate 6.0, the laziness of such associations is properly respected, regardless of the fetch mechanism.
|
|
Backwards compatibility can be achieved by specifying `lazy="false"` or `@ManyToOne(fetch = EAGER)`/`@OneToOne(fetch = EAGER)`/`@OneToMany(fetch = EAGER)`/`@ManyToMany(fetch = EAGER)`.
|
|
|
|
== hbm.xml <return-join/> behavior change
|
|
|
|
As of Hibernate 6.0, a `<return-join/>` will cause a fetch of an association, rather than adding a selection item.
|
|
Consider the following example:
|
|
|
|
```xml
|
|
<sql-query name="organizationreturnproperty">
|
|
<return alias="org" class="Organization">
|
|
<return-property name="id" column="ORGID"/>
|
|
<return-property name="name" column="NAME"/>
|
|
</return>
|
|
<return-join alias="emp" property="org.employments">
|
|
<return-property name="key" column="EMPLOYER"/>
|
|
<return-property name="element" column="EMPID"/>
|
|
<return-property name="element.employee" column="EMPLOYEE"/>
|
|
</return-join>
|
|
...
|
|
</sql-query>
|
|
```
|
|
|
|
Prior to 6.0, a query would return a list of tuples [`Organization`, `Employee`],
|
|
but now this will return a list of `Organization` with an initialized `employments` collection.
|
|
|
|
== Removals
|
|
|
|
The following features have been removed
|
|
|
|
== hbm.xml multiple <column/> now disallowed
|
|
|
|
In 6.0 the support for basic property mappings with multiple columns was removed. The only use case for that was when a
|
|
`CompositeUserType` was in use, which was reworked to now work on top of components.
|
|
|
|
Uses like:
|
|
|
|
```xml
|
|
<property name="salary" type="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
|
|
<column name="CURRENCY"/>
|
|
<column name="AMOUNT" sql-type="float"/>
|
|
</property>
|
|
```
|
|
|
|
have to be migrated to proper components:
|
|
|
|
```xml
|
|
<component name="salary" class="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
|
|
<property name="value" column="AMOUNT">
|
|
<type name="float"/>
|
|
</property>
|
|
<property name="currency" column="CURRENCY"/>
|
|
</component>
|
|
```
|
|
|
|
The component class attribute now supports interpreting a `CompositeUserType` class properly.
|
|
|
|
=== Legacy Hibernate Criteria API
|
|
|
|
The legacy Hibernate Criteria API which was deprecated back in Hibernate 5.x and removed in 6.0.
|
|
Usually, all queries using the legacy API can be modeled with the JPA Criteria API.
|
|
In some cases it is necessary to use the Hibernate JPA Criteria extensions.
|
|
|
|
[[query-iterate]]
|
|
=== Iterate
|
|
|
|
The `Query#iterate()` method has been removed. The alternative is to use one of
|
|
|
|
* `Query#stream()`
|
|
* `Query#getResultStream()`
|
|
* Get the `Iterator` from `List` returned by `Query#list()` / `Query#getResultList()`
|
|
|
|
|
|
[[proc-call-nativequery]]
|
|
=== Callable via NativeQuery
|
|
|
|
Using `NativeQuery` to call SQL functions and procedures is no longer
|
|
supported. `org.hibernate.procedure.ProcedureCall` or
|
|
`jakarta.persistence.StoredProcedureQuery` should be used instead.
|
|
|
|
`@NamedNativeQuery` references defining execution of procedure or
|
|
functions should be migrated to use `@NamedStoredProcedureQuery`
|
|
instead.
|
|
|
|
E.g., the following `@NamedNativeQuery` -
|
|
|
|
```
|
|
@NamedNativeQuery(
|
|
name = "personAndPhones",
|
|
query = "{ ? = call fn_person_and_phones( ? ) }",
|
|
callable = true,
|
|
resultSetMapping = "personWithPhonesResultMapping"
|
|
)
|
|
|
|
...
|
|
|
|
final List<Object[]> personAndPhones = entityManager
|
|
.createNamedQuery("personAndPhones" )
|
|
.setParameter( 1, 1L )
|
|
.getResultList();
|
|
```
|
|
|
|
should be changed to use `@NamedStoredProcedureQuery` instead -
|
|
|
|
```
|
|
@NamedStoredProcedureQuery(
|
|
name = "personAndPhones",
|
|
procedureName = "fn_person_and_phones",
|
|
resultSetMappings = "personWithPhonesResultMapping",
|
|
hints = @QueryHint(name = "org.hibernate.callableFunction", value = "true"),
|
|
parameters = @StoredProcedureParameter(type = Long.class)
|
|
)
|
|
```
|
|
|
|
Callable named native queries in hbm.xml files should be migrated to the orm.xml version.
|
|
|
|
E.g., the following `<sql-query callable="true">` -
|
|
|
|
```
|
|
<sql-query name="simpleScalar" callable="true">
|
|
<return-scalar column="name" type="string"/>
|
|
<return-scalar column="`value`" type="long"/>
|
|
{ ? = call simpleScalar(:number) }
|
|
</sql-query>
|
|
|
|
...
|
|
|
|
final List<Object[]> results = entityManager
|
|
.createNamedQuery("simpleScalar" )
|
|
.setParameter( 1, 1L )
|
|
.getResultList();
|
|
```
|
|
|
|
should be changed to use `<named-stored-procedure-query/>` instead -
|
|
|
|
```xml
|
|
<named-stored-procedure-query name="simpleScalar" procedure-name="simpleScalar">
|
|
<parameter class="java.lang.Integer" mode="IN" name="number"/>
|
|
<result-set-mapping>simpleScalar</result-set-mapping>
|
|
<hint name="org.hibernate.callableFunction" value="true"/>
|
|
</named-stored-procedure-query>
|
|
<sql-result-set-mapping name="simpleScalar">
|
|
<column-result name="name" class="java.lang.String"/>
|
|
<column-result name="value" class="java.lang.Long"/>
|
|
</sql-result-set-mapping>
|
|
```
|
|
|
|
TIP: To ease the migration, `<sql-query callable="true"/>` and `@NamedNativeQuery(callable = true)` queries
|
|
will be translated and registered as named stored procedure in 6.0, but future versions will drop this automatic translation.
|
|
|
|
Either `org.hibernate.procedure.ProcedureCall` or `jakarta.persistence.StoredProcedureQuery`
|
|
can be used to execute the named query -
|
|
|
|
```
|
|
// Use StoredProcedureQuery
|
|
final List<Object[]> personAndPhones = entityManager
|
|
.createNamedStoredProcedureQuery( "simpleScalar" )
|
|
.setParameter( 1, 1L )
|
|
.getResultList();
|
|
|
|
// Use ProcedureCall
|
|
final List<Object[]> personAndPhones = entityManager
|
|
.unwrap( Session.class )
|
|
.getNamedProcedureCall( "simpleScalar" )
|
|
.setParameter( 1, 1L )
|
|
.getResultList();
|
|
```
|
|
|
|
|
|
It is also no longer supported to execute procedures and functions
|
|
via a dynamic (unnamed) `NativeQuery`. All such usages should be converted
|
|
to use `ProcedureCall` or `StoredProcedureQuery` instead via
|
|
`Session#createStoredProcedureCall` or `EntityManager#createStoredProcedureQuery`,
|
|
respectively.
|
|
|
|
```
|
|
// Use StoredProcedureQuery
|
|
final List<Object[]> personAndPhones = entityManager
|
|
.createStoredProcedureQuery( "fn_person_and_phones", "personWithPhonesResultMapping" )
|
|
.setParameter( 1, 1L )
|
|
.getResultList();
|
|
|
|
// Use ProcedureCall
|
|
final List<Object[]> personAndPhones = entityManager
|
|
.unwrap( Session.class )
|
|
.createStoredProcedureCall( "fn_person_and_phones", "personWithPhonesResultMapping" )
|
|
.setParameter( 1, 1L )
|
|
.getResultList();
|
|
```
|
|
|
|
|
|
=== HQL fetch all properties clause
|
|
|
|
The `fetch all properties` clause was removed from the HQL language without a replacement.
|
|
A similar behavior can be achieved by constructing an entity graph and applying that as load graph:
|
|
|
|
```java
|
|
EntityGraph<Document> entityGraph = entityManager.createEntityGraph( Document.class );
|
|
for ( Attribute<Document, ?> attr : entityManager.getMetamodel().entity( Document.class ).getAttributes() ) {
|
|
entityGraph.addAttributeNodes( attr.getName() );
|
|
}
|
|
List<Document> documents = s.createQuery( "from Document", Document.class )
|
|
.setHint( "jakarta.persistence.loadgraph", entityGraph )
|
|
.getResultList();
|
|
```
|
|
|
|
=== JMX integration
|
|
|
|
Hibernate no longer provides built-in support for integrating itself with JMX environments.
|
|
|
|
=== JACC integration
|
|
|
|
Hibernate no longer provides built-in support for integrating itself with JACC environments.
|
|
|
|
|
|
=== Previously Deprecated features
|
|
|
|
* 'hibernate.classLoader.application', 'hibernate.classLoader.resources', 'hibernate.classLoader.hibernate' and 'hibernate.classLoader.environment': use 'hibernate.classLoaders' instead.
|
|
* 'hibernate.hbm2dll.create_namespaces': use 'jakarta.persistence.create-database-schemas' or 'hibernate.hbm2ddl.create_namespaces'
|
|
|
|
// todo (6.0) - surely there are more than this...
|