hibernate-orm/migration-guide.adoc

1123 lines
45 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.
As of 6.0, Hibernate by default creates a sequence per entity hierarchy instead of a single sequence `hibernate_sequence`.
Due to this change, users that previously used `@GeneratedValue(strategy = GenerationStrategy.AUTO)` or simply `@GeneratedValue` (since `AUTO` is the default),
need to ensure that the database now contains sequences for every entity, named `<entity name>_seq`. For an entity `Person`, a sequence `person_seq` is expected to exist. It's best to run hbm2ddl (e.g. by temporarily setting `hbm2ddl.auto=create`) to obtain a list of DDL statements for the sequences.
Users that use a `import.sql` file to import test or static data during application boot or for tests have to make sure the new sequences match expectations of Hibernate. If a `import.sql` file contains references to the `hibernate_sequence`, these have to be replaced with `<entity name>_seq`. Also note that the <<id-sequence-defaults,default allocation size changed>> for such implicit sequences.
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
[[id-sequence-defaults]]
== Defaults for implicit sequence generators
Implicit sequences, like the `hibernate_sequence` before, now respect the defaults of the JPA `@SequenceGenerator` annotation,
which means that the sequences have an allocation size of 50.
Users that use a `import.sql` file to import test or static data during application boot or for tests have to make sure the new sequences match expectations of Hibernate. If a `import.sql` file contains inserts to a table, the sequence for that table has to be reset to `<max id value of table> + 1 + <allocation size>`. This is because Hibernate interprets the value returned by the sequence as hi-value for its pooled optimizer. An alternative is to reset the sequence to `<max id value of table> + 1` and also configure the sequence generator to that initial value, by specifying `@SequenceGenerator(sequenceName = "<entity name>_seq", initialValue = <max id value of table> + 1)` on every entity.
Users are recommended to do the former and add statements like `alter sequence <entity name>_seq restart with <max id value of table> + 1 + <allocation size>` e.g. `alter sequence person_seq restart with 53` if id values 1 and 2 are used.
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 `hibernate.id.db_structure_naming_strategy=legacy`.
[[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`
[[bigint-long-mapping]]
=== SQL `BIGINT`/`count()` mapping changes
The type code `SqlType.BIGINT` is now mapped to the Java type `Long` by default; it previously was (incorrectly) mapped to `BigInteger`.
As a result, native queries whose "select" clause returns a `BIGINT` element will now convert that element to `Long`,
instead of `BigInteger` previously. This will be the case for native queries returning a `count()` in particular.
=== 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 `BIGINT`.
=== 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-update-from]]
==== FROM token now disallowed for UPDATE
In Hibernate 5, the optional keyword `FROM` was allowed for `update` statements like `update from MyEntity e set e.attr = null`.
Support for this was removed to more align with JPQL. Just remove the `from` keyword from your existing `update` statements.
[[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-path-comparison]]
=== Query Path comparison
Prior to 6.0 it was possible to compare two paths of different types or comparing an entity path against a literal e.g.:
```
session.createQuery( "select a from MyEntity a, MyEntity b where a = b.id" )
```
or
```
session.createQuery( "select a from MyEntity a where a = 123")
```
This is not allowed anymore. The queries have to be changed to
```
session.createQuery( "select a from MyEntity a, MyEntity b where a = b" )
```
and
```
session.createQuery( "select a from MyEntity a where a.id = 123");
```
The same is true for the left and right hand side types of a comparison predicate. Queries like
```
Root<Participation> root = criteria.from( Participation.class );
Root<Submission> rootSubQuery = subQuery.from( Submission.class );
subQuery.select( rootSubQuery.join( "submitters" ) );
// left hand side of type Submission.id and right hand side of Submissions types
criteria.where( root.get( "id" ).in( subQuery ) );
```
will be considered invalid and should be changed into
```
Root<Participation> root = criteria.from( Participation.class );
Root<Submission> rootSubQuery = subQuery.from( Submission.class );
subQuery.select( rootSubQuery.join( "submitters" ) );
// left hand side of type Submission.id and right hand side of Submissions types
criteria.where( root.in( subQuery ) );
```
[[query-sqm-pseudo-attr]]
==== Collection pseudo-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.
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.
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.
[[release-artifacts]]
=== Release artifacts
We no longer publishing zip and tgz bundles to SourceForge.
We now publish additional documentation artifacts, such as:
- This link:{migration-guide-url}[Migration Guide]
- The generated link:{docs-url}/logging/logging.html[logging] report
- The generated link:{docs-url}/incubating/incubating.txt[incubation] report
- The generated link:{docs-url}/internals/internal.txt[internals] report
=== 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'
[[configs]]
== Configuration property renames
Some configuration properties that were deprecated for a long time were finally removed for consistency reasons:
|===
| Old property | New property
| `hibernate.ejb.metamodel.population` | `hibernate.jpa.metamodel.population`
| `hibernate.ejb.cfgfile` | `hibernate.cfg_xml_file`
| `hibernate.ejb.xml_files` | `hibernate.orm_xml_files`
| `hibernate.hbmxml.files` | `hibernate.hbm_xml_files`
| `hibernate.ejb.loaded.classes` | `hibernate.loaded_classes`
| `hibernate.ejb.persistenceUnitName` | `hibernate.persistenceUnitName`
| `hibernate.ejb.discard_pc_on_close` | `hibernate.discard_pc_on_close`
| `hibernate.ejb.entitymanager_factory_name` | `hibernate.session_factory_name`
| `hibernate.ejb.session_factory_observer` | `hibernate.session_factory_observer`
| `hibernate.ejb.identifier_generator_strategy_provider` | `hibernate.identifier_generator_strategy_provider`
|===
There are also some property prefixes where the deprecated variant was finally removed:
|===
| Old prefix | New prefix
| `hibernate.ejb.classcache` | `hibernate.classcache`
| `hibernate.ejb.collectioncache` | `hibernate.collectioncache`
| `hibernate.ejb.event` | `hibernate.event`
|===
We decided this is the right time since 6.0 is a major release.
== Dialects
[[community-dialects]]
=== "Community" dialects moved to a separate module
As of Hibernate 6.0, some dialect classes that are maintained by vendors or individuals, as opposed to the Hibernate team.
have moved to a separate Maven artifact: `org.hibernate.orm:hibernate-community-dialects`.
For more information, see https://github.com/hibernate/hibernate-orm/blob/6.0/dialects.adoc#community-dialects.
[[version-specific-and-spatial-dialects]]
=== Version-specific and spatial-specific dialects are deprecated
As of Hibernate 6.0, dialects can detect and adapt to the version of the database in use and its spatial capabilities.
As a result, version-specific dialects (e.g. `org.hibernate.dialect.PostgreSQL91Dialect`)
or spatial-specific dialects (e.g. `org.hibernate.spatial.dialect.postgis.PostgisPG94Dialect`) should no longer be used.
Use these dialects instead, and ignore their deprecated subclasses:
* `org.hibernate.dialect.CockroachDialect`
* `org.hibernate.dialect.DB2Dialect`
* `org.hibernate.dialect.DB2iDialect`
* `org.hibernate.dialect.DB2zDialect`
* `org.hibernate.dialect.DerbyDialect`
* `org.hibernate.dialect.H2Dialect`
* `org.hibernate.dialect.HANAColumnStoreDialect`
* `org.hibernate.dialect.HANARowStoreDialect`
* `org.hibernate.dialect.HSQLDialect`
* `org.hibernate.dialect.MariaDBDialect`
* `org.hibernate.dialect.MySQLDialect`
* `org.hibernate.dialect.OracleDialect`
* `org.hibernate.dialect.PostgreSQLDialect`
* `org.hibernate.dialect.PostgresPlusDialect`
* `org.hibernate.dialect.SpannerDialect`
* `org.hibernate.dialect.SQLServerDialect`
* `org.hibernate.dialect.SQLServerDialect`
* `org.hibernate.dialect.SybaseAnywhereDialect`
* `org.hibernate.dialect.SybaseASEDialect`
The same recommendation applies for <<community-dialects,community-supported dialects>>;
use these and not their deprecated subclasses:
* `org.hibernate.community.dialect.CacheDialect`
* `org.hibernate.community.dialect.CUBRIDDialect`
* `org.hibernate.community.dialect.FirebirdDialect`
* `org.hibernate.community.dialect.IngresDialect`
* `org.hibernate.community.dialect.InformixDialect`
* `org.hibernate.community.dialect.MaxDBDialect`
* `org.hibernate.community.dialect.MimerSQLDialect`
* `org.hibernate.community.dialect.RDMSOS2200Dialect`
* `org.hibernate.community.dialect.SQLiteDialect`
* `org.hibernate.community.dialect.TeradataDialect`
* `org.hibernate.community.dialect.TimesTenDialect`
=== `NUMBER(n,0)` on Oracle
In the absence of other information, Hibernate now treats columns of type `NUMBER(n,0)` as the JDBC type `INTEGER` when `n` is smaller than 11 and as type `BIGINT` when `n` is smaller than 20. Previously, all `NUMBER` types were mapped to `DECIMAL`.
[[multitenancy]]
== Multitenancy simplification
link:{userGuideBase}#multitenacy[Multitenancy in Hibernate ORM] has been simplified.
Hibernate will now infer whether multitenancy is enabled or not automatically:
* If a link:{userGuideBase}#multitenacy-hibernate-MultiTenantConnectionProvider[`MultiTenantConnectionProvider`]
is configured, Hibernate ORM will assume either link:{userGuideBase}#multitenacy-separate-database[database-]
or link:{userGuideBase}#multitenacy-separate-schema[schema-based]
multitenancy (there is no difference between those two as far as Hibernate ORM is concerned).
* If an entity property is annotated with the new `@TenantId` annotation,
Hibernate ORM will assume link:{userGuideBase}#multitenacy-hibernate-TenantId[discriminator-based multitenancy]
(which is a new feature).
To migrate:
* Remove any reference to the configuration property `hibernate.multiTenancy` (`AvailableSettings.MULTI_TENANT`)
or to `MultiTenancyStrategy` from your application: they are no longer necessary.
`hibernate.multiTenancy` will be ignored
and `AvailableSettings.MULTI_TENANT`/`MultiTenancyStrategy` will lead to compilation errors since they have been removed.
* If your application was somehow achieving discriminator-based multitenancy through custom code (e.g. custom filters),
consider relying on the new `@TenantId` annotation
and link:{userGuideBase}#multitenacy-hibernate-session-configuration[setting the session's tenant ID] instead.
[[where-annotation]]
== @Where annotation
The link:{userGuideBase}#pc-where[@Where annotation] was always meant as a way of supporting "soft deletes",
allowing the records of an entity which do not respect a simple `WHERE` clause to be completely ignored by Hibernate.
In Hibernate 5.x, this was true only when *fetching* entities or collections of entities having this annotation.
As of Hibernate 6.0, the clause specified by a `@Where` applied to an entity type is used in *all* interactions Hibernate has the with that entity type. This includes, for instance, when deleting or updating records using mutation queries (e.g. `DELETE FROM MyEntity where id = :id`), for which in 5 the annotation's clause was not applied.
This is consistent with the purpose of the annotation, which is to always disregard records not respecting the specified condition.
// todo (6.0) - surely there are more than this...