Skip to content

Commit 5102217

Browse files
committed
HHH-16383 - NaturalIdClass
1 parent 1c548c0 commit 5102217

File tree

104 files changed

+3871
-2637
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+3871
-2637
lines changed

documentation/src/main/asciidoc/introduction/Entities.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,21 @@ Hibernate automatically generates a `UNIQUE` constraint on the columns mapped by
526526
Consider using the natural id attributes to implement <<equals-and-hash>>.
527527
====
528528

529+
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>> -
530+
531+
[source,java]
532+
----
533+
record BookKey(String isbn, int printing) {}
534+
535+
@Entity
536+
@NaturalIdClass(BookKey.class)
537+
class Book {
538+
...
539+
}
540+
----
541+
542+
See the link:{doc-user-guide-url}#naturalid[User Guide] for more details about natural ids.
543+
529544
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.
530545

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

documentation/src/main/asciidoc/introduction/Interacting.adoc

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ Modifications are automatically detected when the session is <<flush,flushed>>.
276276

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

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

372+
[[load-by-natural-id]]
373+
As discussed <<natural-id-attributes,earlier>>, Hibernate offers the ability to map a natural id and perform load operations using that natural id.
374+
This is accomplished using the `KeyType#NATURAL` `FindOption` -
375+
376+
[source,java]
377+
----
378+
var bookKey = new BookKey(...);
379+
var book = session.find(Book.class, bookKey, NATURAL);
380+
var books = session.findMultiple(Book.class, List.of(bookKey), NATURAL);
381+
----
382+
383+
When loading by natural id, the type of value accepted depends on the type of natural id.
384+
For single-attribute natural ids, whether defined by a basic or embedded type, the attribute type should be used.
385+
For multi-attribute natural ids, Hibernate will accept a number of forms:
386+
387+
* If a link:{doc-javadoc-url}org/hibernate/annotations/NaturalIdClass.html[`@NaturalIdClass`] is defined, an instance of the natural id class may be used.
388+
* A `List` of the individual attribute values, ordered alphabetically by name, may be used.
389+
* A `Map` of the individual attribute values, keyed by the attribute name, may be used.
390+
371391
Each of the operations we've seen so far affects a single entity instance passed as an argument.
372392
But there's a way to set things up so that an operation will propagate to associated entities.
373393

394+
See the link:{doc-user-guide-url}#find-by-natural-id[User Guide] for more details about loading by natural ids.
395+
374396
[[cascade]]
375397
=== Cascading persistence operations
376398

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

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

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

653+
Again, see the link:{doc-user-guide-url}#find-by-natural-id[User Guide] for more details about loading by natural ids.
654+
630655
Here's how we can retrieve an entity by its composite natural id:
631656

632657
[source,java]
Lines changed: 69 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
11
[[naturalid]]
22
=== Natural Ids
33
:core-project-dir: {root-project-dir}/hibernate-core
4-
:example-dir-naturalid: {core-project-dir}/src/test/java/org/hibernate/orm/test/mapping/identifier
4+
:core-test-base-dir: {core-project-dir}/src/test/java/org/hibernate/orm/test
5+
:example-dir-naturalid: {core-test-base-dir}/mapping/identifier
56
:jcache-project-dir: {root-project-dir}/hibernate-jcache
67
:example-dir-caching: {jcache-project-dir}/src/test/java/org/hibernate/orm/test/caching
78
:extrasdir: extras
89

9-
Natural ids represent domain model unique identifiers that have a meaning in the real world too.
10+
Natural ids are unique identifiers in the domain model that have a meaning in the real world too.
1011
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.
11-
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).
12-
13-
[IMPORTANT]
14-
====
15-
All values used in a natural id must be non-nullable.
16-
17-
For natural id mappings using a to-one association, this precludes the use of not-found
18-
mappings which effectively define a nullable mapping.
19-
====
12+
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).
2013

2114
[[naturalid-mapping]]
2215
==== Natural Id Mapping
@@ -50,104 +43,112 @@ include::{example-dir-naturalid}/MultipleNaturalIdTest.java[tags=naturalid-multi
5043
----
5144
====
5245

53-
[[naturalid-api]]
54-
==== Natural Id API
55-
56-
As stated before, Hibernate provides an API for loading entities by their associated natural id.
57-
This is represented by the `org.hibernate.NaturalIdLoadAccess` contract obtained via Session#byNaturalId.
46+
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>>.
5847

59-
[NOTE]
60-
====
61-
If the entity does not define a natural id, trying to load an entity by its natural id will throw an exception.
62-
====
63-
64-
[[naturalid-load-access-example]]
65-
.Using NaturalIdLoadAccess
48+
[[naturalidclass-example]]
49+
.Natural id with @NaturalIdClass
6650
====
6751
[source,java]
6852
----
69-
include::{example-dir-naturalid}/SimpleNaturalIdTest.java[tags=naturalid-load-access-example,indent=0]
53+
include::{core-test-base-dir}/mapping/naturalid/idclass/SimpleNaturalIdClassTests.java[tags=naturalidclass-mapping-example,indent=0]
7054
----
55+
====
7156

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

58+
[[natural-id-mutability]]
59+
==== Natural id mutability
60+
61+
A natural id may be mutable or immutable. By default, the `@NaturalId` annotation marks the attribute as immutable.
62+
An immutable natural id is expected to never change its value.
63+
In fact, Hibernate will check at flush-time to ensure that the value has not been altered.
64+
65+
If the value(s) of the natural id attribute(s) may change, `@NaturalId(mutable = true)` should be used instead.
66+
67+
[[naturalid-mutable-mapping-example]]
68+
.Mutable natural id mapping
69+
====
7770
[source,java]
7871
----
79-
include::{example-dir-naturalid}/MultipleNaturalIdTest.java[tags=naturalid-load-access-example,indent=0]
72+
include::{example-dir-naturalid}/MutableNaturalIdTest.java[tags=naturalid-mutable-mapping-example,indent=0]
8073
----
8174
====
8275

83-
NaturalIdLoadAccess offers 2 distinct methods for obtaining the entity:
8476

85-
`load()`:: obtains a reference to the entity, making sure that the entity state is initialized.
86-
`getReference()`:: obtains a reference to the entity. The state may or may not be initialized.
87-
If the entity is already associated with the current running Session, that reference (loaded or not) is returned.
88-
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.
8977

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

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

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

97-
[[naturalid-simple-load-access-example]]
98-
.Loading by simple natural id
81+
82+
83+
84+
85+
86+
87+
88+
89+
90+
91+
92+
[[natural-id-caching]]
93+
==== Natural id resolution caching
94+
95+
Within the Session, Hibernate maintains a cross reference of the resolutions from natural id values to entity identifier (PK) values.
96+
We can also have this value resolution cached in the second level cache if second level caching is enabled.
97+
98+
[[naturalid-caching]]
99+
.Natural id caching
99100
====
100101
[source,java]
101102
----
102-
include::{example-dir-naturalid}/SimpleNaturalIdTest.java[tags=naturalid-simple-load-access-example,indent=0]
103+
include::{example-dir-caching}/CacheableNaturalIdTest.java[tags=naturalid-cacheable-mapping-example,indent=0]
103104
----
105+
====
104106

105-
[source,java]
106-
----
107-
include::{example-dir-naturalid}/CompositeNaturalIdTest.java[tags=naturalid-simple-load-access-example,indent=0]
108-
----
107+
[IMPORTANT]
108+
====
109+
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.
109110
====
110111

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

114-
`SimpleNaturalIdLoadAccess` is similar to `NaturalIdLoadAccess` except that it does not define the using method.
115-
Instead, because these _simple_ natural ids are defined based on just one attribute we can directly pass
116-
the corresponding natural id attribute value directly to the `load()` and `getReference()` methods.
113+
114+
[[find-by-natural-id]]
115+
[[naturalid-api]]
116+
==== Loading by natural id
117+
118+
Hibernate provides a means to load one or more entities by natural id using the `KeyType.NATURAL` `FindOption` passed to `find()` or `findMultiple()`.
117119

118120
[NOTE]
119121
====
120-
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.
122+
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.
121123
====
122124

123-
[[naturalid-mutability-caching]]
124-
==== Natural Id - Mutability and Caching
125-
126-
A natural id may be mutable or immutable. By default the `@NaturalId` annotation marks an immutable natural id attribute.
127-
An immutable natural id is expected to never change its value.
128-
129-
If the value(s) of the natural id attribute(s) change, `@NaturalId(mutable = true)` should be used instead.
130125

131-
[[naturalid-mutable-mapping-example]]
132-
.Mutable natural id mapping
126+
[[find-by-natural-id-example]]
127+
.Loading by natural id
133128
====
134129
[source,java]
135130
----
136-
include::{example-dir-naturalid}/MutableNaturalIdTest.java[tags=naturalid-mutable-mapping-example,indent=0]
131+
include::{example-dir-naturalid}/SimpleNaturalIdTest.java[tags=naturalid-loading-example,indent=0]
137132
----
138133
====
139134

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

143-
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.
144-
To be clear: this is only pertinent for mutable natural ids.
137+
* For single-attribute natural ids, whether defined by a basic or embedded type, the attribute type should be used.
138+
* For multi-attribute natural ids, Hibernate will accept a number of forms:
139+
140+
** If a link:{doc-javadoc-url}org/hibernate/annotations/NaturalIdClass.html[`@NaturalIdClass`] is defined, an instance of the natural id class may be used.
141+
** A `List` of the individual attribute values, ordered alphabetically by name, may be used.
142+
** A `Map` of the individual attribute values, keyed by the attribute name, may be used.
143+
144+
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.
145+
To work around this condition, Hibernate will attempt to discover any such pending changes and adjust them prior to performing the load.
145146

146147
[IMPORTANT]
147148
====
148-
This _discovery and adjustment_ have a performance impact.
149-
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`).
150-
This will force Hibernate to circumvent the checking of mutable natural ids.
149+
This _discovery and adjustment_ (synchronization) has a performance impact.
150+
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.
151+
To be clear: this is only pertinent for mutable natural ids.
151152
====
152153

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

162-
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.
163-
164-
[[naturalid-caching]]
165-
.Natural id caching
166-
====
167-
[source,java]
168-
----
169-
include::{example-dir-caching}/CacheableNaturalIdTest.java[tags=naturalid-cacheable-mapping-example,indent=0]
170-
----
171-
====
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate;
6+
7+
import jakarta.persistence.FindOption;
8+
9+
/// FindOption allowing to load based on either id (default) or natural-id.
10+
///
11+
/// @see jakarta.persistence.EntityManager#find
12+
/// @see Session#findMultiple
13+
///
14+
/// @since 7.3
15+
///
16+
/// @author Steve Ebersole
17+
/// @author Gavin King
18+
public enum KeyType implements FindOption {
19+
/// Indicates to find by the entity's identifier. The default.
20+
///
21+
/// @see jakarta.persistence.Id
22+
/// @see jakarta.persistence.EmbeddedId
23+
/// @see jakarta.persistence.IdClass
24+
IDENTIFIER,
25+
26+
/// Indicates to find based on the entity's natural-id, if one.
27+
///
28+
/// @see org.hibernate.annotations.NaturalId
29+
/// @see org.hibernate.annotations.NaturalIdClass
30+
///
31+
/// @implSpec Will trigger an [IllegalArgumentException] if the entity does
32+
/// not define a natural-id.
33+
NATURAL
34+
}

hibernate-core/src/main/java/org/hibernate/NaturalIdLoadAccess.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package org.hibernate;
66

77
import jakarta.persistence.EntityGraph;
8-
98
import jakarta.persistence.PessimisticLockScope;
109
import jakarta.persistence.Timeout;
1110
import jakarta.persistence.metamodel.SingularAttribute;
@@ -35,7 +34,10 @@
3534
* @see Session#byNaturalId(Class)
3635
* @see org.hibernate.annotations.NaturalId
3736
* @see SimpleNaturalIdLoadAccess
37+
*
38+
* @deprecated (since 7.3) Use {@linkplain Session#findByNaturalId} instead.
3839
*/
40+
@Deprecated
3941
public interface NaturalIdLoadAccess<T> {
4042

4143
/**

hibernate-core/src/main/java/org/hibernate/NaturalIdMultiLoadAccess.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@
3636
*
3737
* @see Session#byMultipleNaturalId(Class)
3838
* @see org.hibernate.annotations.NaturalId
39+
*
40+
* @deprecated (since 7.3) Use {@linkplain Session#findMultipleByNaturalId} instead.
3941
*/
42+
@Deprecated
4043
public interface NaturalIdMultiLoadAccess<T> {
4144

4245
/**
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate;
6+
7+
import jakarta.persistence.FindOption;
8+
9+
/// Indicates whether to perform synchronization (a sort of flush)
10+
/// before a [find by natural-id][KeyType#NATURAL].
11+
///
12+
/// @author Steve Ebersole
13+
public enum NaturalIdSynchronization implements FindOption {
14+
/// Perform the synchronization.
15+
ENABLED,
16+
17+
/// Do not perform the synchronization.
18+
DISABLED;
19+
}

0 commit comments

Comments
 (0)