Skip to content

Commit 9424258

Browse files
committed
HHH-16383 - NaturalIdClass
HHH-7287 - Problem in caching proper natural-id-values when obtaining result by naturalIdQuery
1 parent 3d43c69 commit 9424258

File tree

5 files changed

+60
-21
lines changed

5 files changed

+60
-21
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ For single-attribute natural ids, whether defined by a basic or embedded type, t
385385
For multi-attribute natural ids, Hibernate will accept a number of forms:
386386

387387
* 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.
388+
* An array of the individual attribute values, ordered alphabetically by name, may be used.
389389
* A `Map` of the individual attribute values, keyed by the attribute name, may be used.
390390

391391
Each of the operations we've seen so far affects a single entity instance passed as an argument.

documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,21 +74,6 @@ include::{example-dir-naturalid}/MutableNaturalIdTest.java[tags=naturalid-mutabl
7474
====
7575

7676

77-
78-
79-
80-
81-
82-
83-
84-
85-
86-
87-
88-
89-
90-
91-
9277
[[natural-id-caching]]
9378
==== Natural id resolution caching
9479

@@ -138,7 +123,7 @@ When loading by natural id, the type of value accepted depends on the definition
138123
* For multi-attribute natural ids, Hibernate will accept a number of forms:
139124

140125
** 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.
126+
** An array of the individual attribute values, ordered alphabetically by name, may be used.
142127
** A `Map` of the individual attribute values, keyed by the attribute name, may be used.
143128

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

hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/idclass/SimpleNaturalIdClassTests.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import java.time.Instant;
2525
import java.util.List;
26+
import java.util.Map;
2627

2728
import static org.assertj.core.api.Assertions.assertThat;
2829
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -111,7 +112,7 @@ void testNormalization(SessionFactoryScope factoryScope) {
111112
stats.clear();
112113

113114
factoryScope.inTransaction( (session) -> {
114-
session.find( SystemUser.class, new SystemUserKey("steve", "ci"), KeyType.NATURAL );
115+
session.find( SystemUser.class, new SystemUserKey("ci", "steve"), KeyType.NATURAL );
115116
assertEquals( 1, stats.getNaturalIdStatistics( SystemUser.class.getName() ).getNormalizationCount() );
116117
} );
117118
}
@@ -126,6 +127,23 @@ void testFindByClassWithToOne(SessionFactoryScope factoryScope) {
126127
} );
127128
}
128129

130+
@Test
131+
void testFindByCompositeNaturalIdForms(SessionFactoryScope factoryScope) {
132+
factoryScope.inTransaction( (session) -> {
133+
// by idclass is tested in other methods...
134+
135+
var customer = session.getReference( Customer.class, 1 );
136+
137+
// by map
138+
session.find( SystemUser.class, Map.of( "system", "ci", "username", "steve" ), KeyType.NATURAL );
139+
session.find( Order.class, Map.of( "customer", customer, "invoiceNumber", 1001), KeyType.NATURAL );
140+
141+
// by array
142+
session.find( SystemUser.class, new Object[] {"ci","steve"}, KeyType.NATURAL );
143+
session.find( Order.class, new Object[] {customer,1001}, KeyType.NATURAL );
144+
} );
145+
}
146+
129147
@SuppressWarnings("FieldCanBeLocal")
130148
@Entity(name="User")
131149
@Table(name="t_users")

hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/mutable/MutableNaturalIdTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package org.hibernate.orm.test.mapping.naturalid.mutable;
66

77
import org.hibernate.HibernateException;
8+
import org.hibernate.KeyType;
89
import org.hibernate.LockMode;
910
import org.hibernate.engine.spi.SessionFactoryImplementor;
1011
import org.hibernate.metamodel.mapping.EntityMappingType;
@@ -20,6 +21,7 @@
2021
import org.junit.jupiter.api.Test;
2122

2223
import java.lang.reflect.Field;
24+
import java.util.Map;
2325

2426
import static org.hibernate.cfg.AvailableSettings.GENERATE_STATISTICS;
2527
import static org.hibernate.cfg.AvailableSettings.USE_QUERY_CACHE;
@@ -401,4 +403,38 @@ public void testModificationInOtherSession(SessionFactoryScope factoryScope) {
401403
assertNotNull( session.byNaturalId( User.class ).using( "name", "gavin" ).using( "org", "hb" ).load());
402404
} );
403405
}
406+
407+
@Test
408+
@JiraKey("HHH-7287")
409+
public void testModificationInOtherSession2(SessionFactoryScope factoryScope) {
410+
var id = factoryScope.fromTransaction( (session) -> {
411+
User u = new User( "gavin", "hb", "secret" );
412+
session.persist( u );
413+
return u.getId();
414+
} );
415+
416+
// Use transactionless session
417+
factoryScope.inSession( (session) -> {
418+
// this loads the state into this `session`
419+
var byNaturalId = session.find( User.class, Map.of("name", "gavin", "org", "hb"), KeyType.NATURAL );
420+
assertNotNull( byNaturalId );
421+
422+
// CHANGE natural-id values in another session
423+
factoryScope.inTransaction( (otherSession) -> {
424+
var u = otherSession.find( User.class, id );
425+
u.setOrg( "zz" );
426+
} );
427+
// CHANGE APPLIED
428+
429+
byNaturalId = session.find( User.class, Map.of("name", "gavin", "org", "hb"), KeyType.NATURAL );
430+
assertNotNull( byNaturalId );
431+
432+
// the internal query will 'see' the new values, because isolation level < SERIALIZABLE
433+
var byNaturalId2 = session.find( User.class, Map.of("name", "gavin", "org", "zz"), KeyType.NATURAL );
434+
assertSame( byNaturalId, byNaturalId2 );
435+
436+
// this fails, that's the bug
437+
assertNotNull( session.find( User.class, Map.of("name", "gavin", "org", "hb"), KeyType.NATURAL ) );
438+
} );
439+
}
404440
}

whats-new.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ The new `KeyType` enum is a `FindOption` that allows `find()` and `findMultiple(
2222
@Entity
2323
class Person {
2424
@Id
25-
Integer id;
25+
Integer id;
2626
@NaturalId
27-
String name;
27+
String name;
2828
...
2929
}
3030
@@ -77,5 +77,5 @@ Given this model, we can load Person by natural id via:
7777
----
7878
session.find( Order.class,
7979
new PersonNameKey( "Bilbo", "Baggins" ),
80-
KeyType.NATURAL )
80+
KeyType.NATURAL );
8181
----

0 commit comments

Comments
 (0)