Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions documentation/src/main/asciidoc/introduction/Entities.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,21 @@ Hibernate automatically generates a `UNIQUE` constraint on the columns mapped by
Consider using the natural id attributes to implement <<equals-and-hash>>.
====

In cases where the natural id is defined by multiple attributes, Hibernate also offers the link:{doc-javadoc-url}org/hibernate/annotations/NaturalIdClass.html[`@NaturalIdClass`] annotation which acts similarly to the Jakarta Persistence `@IdClass` annotation for <<load-by-natural-id,find operations>> -

[source,java]
----
record BookKey(String isbn, int printing) {}
@Entity
@NaturalIdClass(BookKey.class)
class Book {
...
}
----

See the link:{doc-user-guide-url}#naturalid[User Guide] for more details about natural ids.

The payoff for doing this extra work, as we will see <<natural-id-cache,much later>>, is that we can take advantage of optimized natural id lookups that make use of the second-level cache.

Note that even when you've identified a natural key, we still recommend the use of a generated surrogate key in foreign keys, since this makes your data model _much_ easier to change.
Expand Down
27 changes: 26 additions & 1 deletion documentation/src/main/asciidoc/introduction/Interacting.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ Modifications are automatically detected when the session is <<flush,flushed>>.

On the other hand, except for `getReference()`, the following operations all result in immediate access to the database:

[[methods-for-reading]]
.Methods for reading and locking data
[%breakable,cols="30,~"]
|===
Expand Down Expand Up @@ -368,9 +369,30 @@ The following code results in a single SQL `select` statement:
List<Book> books = session.findMultiple(Book.class, bookIds);
----

[[load-by-natural-id]]
As discussed <<natural-id-attributes,earlier>>, Hibernate offers the ability to map a natural id and perform load operations using that natural id.
This is accomplished using the `KeyType#NATURAL` `FindOption` -

[source,java]
----
var bookKey = new BookKey(...);
var book = session.find(Book.class, bookKey, NATURAL);
var books = session.findMultiple(Book.class, List.of(bookKey), NATURAL);
----

When loading by natural id, the type of value accepted depends on the type of natural id.
For single-attribute natural ids, whether defined by a basic or embedded type, the attribute type should be used.
For multi-attribute natural ids, Hibernate will accept a number of forms:

* If a link:{doc-javadoc-url}org/hibernate/annotations/NaturalIdClass.html[`@NaturalIdClass`] is defined, an instance of the natural id class may be used.
* A `List` of the individual attribute values, ordered alphabetically by name, may be used.
* A `Map` of the individual attribute values, keyed by the attribute name, may be used.

Each of the operations we've seen so far affects a single entity instance passed as an argument.
But there's a way to set things up so that an operation will propagate to associated entities.

See the link:{doc-user-guide-url}#find-by-natural-id[User Guide] for more details about loading by natural ids.

[[cascade]]
=== Cascading persistence operations

Expand Down Expand Up @@ -595,7 +617,8 @@ Therefore, Hibernate has some APIs that streamline certain more complicated look

[NOTE]
====
Since the introduction of `FindOption` in JPA 3.2, `byId()` is now much less useful.
Since the introduction of `FindOption` in JPA 3.2, `byId()` is now much less useful and deprecated.
Instead, use `find()` and `findMultiple()` as discussed <<methods-for-reading,earlier>>.
====

Batch loading is very useful when we need to retrieve multiple instances of the same entity class by id:
Expand Down Expand Up @@ -627,6 +650,8 @@ We also have some operations for working with lookups by <<natural-identifiers,
| `byMultipleNaturalId()` | Lets us load a _batch_ of natural ids at the same time
|===

Again, see the link:{doc-user-guide-url}#find-by-natural-id[User Guide] for more details about loading by natural ids.

Here's how we can retrieve an entity by its composite natural id:

[source,java]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
[[naturalid]]
=== Natural Ids
:core-project-dir: {root-project-dir}/hibernate-core
:example-dir-naturalid: {core-project-dir}/src/test/java/org/hibernate/orm/test/mapping/identifier
:core-test-base-dir: {core-project-dir}/src/test/java/org/hibernate/orm/test
:example-dir-naturalid: {core-test-base-dir}/mapping/identifier
:jcache-project-dir: {root-project-dir}/hibernate-jcache
:example-dir-caching: {jcache-project-dir}/src/test/java/org/hibernate/orm/test/caching
:extrasdir: extras

Natural ids represent domain model unique identifiers that have a meaning in the real world too.
Natural ids are unique identifiers in the domain model that have a meaning in the real world too.
Even if a natural id does not make a good primary key (surrogate keys being usually preferred), it's still useful to tell Hibernate about it.
As we will see later, Hibernate provides a dedicated, efficient API for loading an entity by its natural id much like it offers for loading by identifier (PK).

[IMPORTANT]
====
All values used in a natural id must be non-nullable.

For natural id mappings using a to-one association, this precludes the use of not-found
mappings which effectively define a nullable mapping.
====
As we will see <<find-by-natural-id,later>>, Hibernate provides an efficient means for loading an entity by its natural id much like it offers for loading by identifier (PK).

[[naturalid-mapping]]
==== Natural Id Mapping
Expand Down Expand Up @@ -50,104 +43,112 @@ include::{example-dir-naturalid}/MultipleNaturalIdTest.java[tags=naturalid-multi
----
====

[[naturalid-api]]
==== Natural Id API

As stated before, Hibernate provides an API for loading entities by their associated natural id.
This is represented by the `org.hibernate.NaturalIdLoadAccess` contract obtained via Session#byNaturalId.
Natural ids defined using multiple persistent attributes may also define a link:{doc-javadoc-url}org/hibernate/annotations/NaturalIdClass.html[`@NaturalIdClass`] which can be used for <<find-by-natural-id,find operations>>.

[NOTE]
====
If the entity does not define a natural id, trying to load an entity by its natural id will throw an exception.
====

[[naturalid-load-access-example]]
.Using NaturalIdLoadAccess
[[naturalidclass-example]]
.Natural id with @NaturalIdClass
====
[source,java]
----
include::{example-dir-naturalid}/SimpleNaturalIdTest.java[tags=naturalid-load-access-example,indent=0]
include::{core-test-base-dir}/mapping/naturalid/idclass/SimpleNaturalIdClassTests.java[tags=naturalidclass-mapping-example,indent=0]
----
====

[source,java]
----
include::{example-dir-naturalid}/CompositeNaturalIdTest.java[tags=naturalid-load-access-example,indent=0]
----

[[natural-id-mutability]]
==== Natural id mutability

A natural id may be mutable or immutable. By default, the `@NaturalId` annotation marks the attribute as immutable.
An immutable natural id is expected to never change its value.
In fact, Hibernate will check at flush-time to ensure that the value has not been altered.

If the value(s) of the natural id attribute(s) may change, `@NaturalId(mutable = true)` should be used instead.

[[naturalid-mutable-mapping-example]]
.Mutable natural id mapping
====
[source,java]
----
include::{example-dir-naturalid}/MultipleNaturalIdTest.java[tags=naturalid-load-access-example,indent=0]
include::{example-dir-naturalid}/MutableNaturalIdTest.java[tags=naturalid-mutable-mapping-example,indent=0]
----
====

NaturalIdLoadAccess offers 2 distinct methods for obtaining the entity:

`load()`:: obtains a reference to the entity, making sure that the entity state is initialized.
`getReference()`:: obtains a reference to the entity. The state may or may not be initialized.
If the entity is already associated with the current running Session, that reference (loaded or not) is returned.
If the entity is not loaded in the current Session and the entity supports proxy generation, an uninitialized proxy is generated and returned, otherwise the entity is loaded from the database and returned.

`NaturalIdLoadAccess` allows loading an entity by natural id and at the same time applies a pessimistic lock.
For additional details on locking, see the <<chapters/locking/Locking.adoc#locking,Locking>> chapter.

We will discuss the last method available on NaturalIdLoadAccess ( `setSynchronizationEnabled()` ) in <<naturalid-mutability-caching>>.

Because the `Book` entities in the first two examples define "simple" natural ids, we can load them as follows:

[[naturalid-simple-load-access-example]]
.Loading by simple natural id











[[natural-id-caching]]
==== Natural id resolution caching

Within the Session, Hibernate maintains a cross reference of the resolutions from natural id values to entity identifier (PK) values.
We can also have this value resolution cached in the second level cache if second level caching is enabled.

[[naturalid-caching]]
.Natural id caching
====
[source,java]
----
include::{example-dir-naturalid}/SimpleNaturalIdTest.java[tags=naturalid-simple-load-access-example,indent=0]
include::{example-dir-caching}/CacheableNaturalIdTest.java[tags=naturalid-cacheable-mapping-example,indent=0]
----
====

[source,java]
----
include::{example-dir-naturalid}/CompositeNaturalIdTest.java[tags=naturalid-simple-load-access-example,indent=0]
----
[IMPORTANT]
====
Think carefully before caching resolutions for natural ids which are partially or fully <<natural-id-mutability,mutable>> in the second level cache as this will often have a negative impact on performance.
====

Here we see the use of the `org.hibernate.SimpleNaturalIdLoadAccess` contract,
obtained via `Session#bySimpleNaturalId()`.

`SimpleNaturalIdLoadAccess` is similar to `NaturalIdLoadAccess` except that it does not define the using method.
Instead, because these _simple_ natural ids are defined based on just one attribute we can directly pass
the corresponding natural id attribute value directly to the `load()` and `getReference()` methods.

[[find-by-natural-id]]
[[naturalid-api]]
==== Loading by natural id

Hibernate provides a means to load one or more entities by natural id using the `KeyType.NATURAL` `FindOption` passed to `find()` or `findMultiple()`.

[NOTE]
====
If the entity does not define a natural id, or if the natural id is not of a "simple" type, an exception will be thrown there.
Hibernate historically offered the dedicated `byNaturalId()`, `bySimpleNaturalId()` and `byMultipleNaturalId()` APIs for loading one or more entities by natural id using its legacy "load access" approach. However, with JPA 3.2 and the introduction of `FindOption`, etc., these "load access" approaches are considered deprecated and are not discussed here.
====

[[naturalid-mutability-caching]]
==== Natural Id - Mutability and Caching

A natural id may be mutable or immutable. By default the `@NaturalId` annotation marks an immutable natural id attribute.
An immutable natural id is expected to never change its value.

If the value(s) of the natural id attribute(s) change, `@NaturalId(mutable = true)` should be used instead.

[[naturalid-mutable-mapping-example]]
.Mutable natural id mapping
[[find-by-natural-id-example]]
.Loading by natural id
====
[source,java]
----
include::{example-dir-naturalid}/MutableNaturalIdTest.java[tags=naturalid-mutable-mapping-example,indent=0]
include::{example-dir-naturalid}/SimpleNaturalIdTest.java[tags=naturalid-loading-example,indent=0]
----
====

Within the Session, Hibernate maintains a mapping from natural id values to entity identifiers (PK) values.
If natural ids values changed, it is possible for this mapping to become out of date until a flush occurs.
When loading by natural id, the type of value accepted depends on the definition of the natural id.

To work around this condition, Hibernate will attempt to discover any such pending changes and adjust them when the `load()` or `getReference()` methods are executed.
To be clear: this is only pertinent for mutable natural ids.
* For single-attribute natural ids, whether defined by a basic or embedded type, the attribute type should be used.
* For multi-attribute natural ids, Hibernate will accept a number of forms:

** If a link:{doc-javadoc-url}org/hibernate/annotations/NaturalIdClass.html[`@NaturalIdClass`] is defined, an instance of the natural id class may be used.
** A `List` of the individual attribute values, ordered alphabetically by name, may be used.
** A `Map` of the individual attribute values, keyed by the attribute name, may be used.

There are a few differences to be aware of when loading by natural id compared to loading by primary key. Most importantly, if the natural id is mutable and its values have changed, it is possible for the resolution caching to become out of date until a flush occurs resulting in incorrect results.
To work around this condition, Hibernate will attempt to discover any such pending changes and adjust them prior to performing the load.

[IMPORTANT]
====
This _discovery and adjustment_ have a performance impact.
If you are certain that none of the mutable natural ids already associated with the current `Session` have changed, you can disable this checking by calling `setSynchronizationEnabled(false)` (the default is `true`).
This will force Hibernate to circumvent the checking of mutable natural ids.
This _discovery and adjustment_ (synchronization) has a performance impact.
If you are certain that none of the mutable natural ids already associated with the current `Session` have changed, you can disable this using the `NaturalIdSynchronization.DISABLED` option which will force Hibernate to skip the checking of mutable natural ids.
To be clear: this is only pertinent for mutable natural ids.
====

[[naturalid-mutable-synchronized-example]]
Expand All @@ -159,13 +160,3 @@ include::{example-dir-naturalid}/MutableNaturalIdTest.java[tags=naturalid-mutabl
----
====

Not only can this NaturalId-to-PK resolution be cached in the Session, but we can also have it cached in the second-level cache if second level caching is enabled.

[[naturalid-caching]]
.Natural id caching
====
[source,java]
----
include::{example-dir-caching}/CacheableNaturalIdTest.java[tags=naturalid-cacheable-mapping-example,indent=0]
----
====
34 changes: 34 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/KeyType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate;

import jakarta.persistence.FindOption;

/// FindOption allowing to load based on either id (default) or natural-id.
///
/// @see jakarta.persistence.EntityManager#find
/// @see Session#findMultiple
///
/// @since 7.3
///
/// @author Steve Ebersole
/// @author Gavin King
public enum KeyType implements FindOption {
/// Indicates to find by the entity's identifier. The default.
///
/// @see jakarta.persistence.Id
/// @see jakarta.persistence.EmbeddedId
/// @see jakarta.persistence.IdClass
IDENTIFIER,

/// Indicates to find based on the entity's natural-id, if one.
///
/// @see org.hibernate.annotations.NaturalId
/// @see org.hibernate.annotations.NaturalIdClass
///
/// @implSpec Will trigger an [IllegalArgumentException] if the entity does
/// not define a natural-id.
NATURAL
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package org.hibernate;

import jakarta.persistence.EntityGraph;

import jakarta.persistence.PessimisticLockScope;
import jakarta.persistence.Timeout;
import jakarta.persistence.metamodel.SingularAttribute;
Expand Down Expand Up @@ -35,7 +34,10 @@
* @see Session#byNaturalId(Class)
* @see org.hibernate.annotations.NaturalId
* @see SimpleNaturalIdLoadAccess
*
* @deprecated (since 7.3) Use {@linkplain Session#findByNaturalId} instead.
*/
@Deprecated
public interface NaturalIdLoadAccess<T> {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@
*
* @see Session#byMultipleNaturalId(Class)
* @see org.hibernate.annotations.NaturalId
*
* @deprecated (since 7.3) Use {@linkplain Session#findMultipleByNaturalId} instead.
*/
@Deprecated
public interface NaturalIdMultiLoadAccess<T> {

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate;

import jakarta.persistence.FindOption;

/// Indicates whether to perform synchronization (a sort of flush)
/// before a [find by natural-id][KeyType#NATURAL].
///
/// @author Steve Ebersole
public enum NaturalIdSynchronization implements FindOption {
/// Perform the synchronization.
ENABLED,

/// Do not perform the synchronization.
DISABLED;
}
Loading
Loading