Skip to content

Commit 16eaf9d

Browse files
committed
HHH-16383 - NaturalIdClass
1 parent 28998b3 commit 16eaf9d

File tree

8 files changed

+288
-28
lines changed

8 files changed

+288
-28
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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)
10+
/// or natural-id.
11+
///
12+
/// @see jakarta.persistence.EntityManager#find
13+
/// @see Session#findMultiple
14+
///
15+
/// @author Steve Ebersole
16+
/// @author Gavin King
17+
public enum FindBy implements FindOption {
18+
/// Indicates to find by the entity's identifier. The default.
19+
///
20+
/// @see jakarta.persistence.Id
21+
/// @see jakarta.persistence.EmbeddedId
22+
/// @see jakarta.persistence.IdClass
23+
ID,
24+
25+
/// Indicates to find based on the entity's natural-id, if one.
26+
///
27+
/// @see org.hibernate.annotations.NaturalId
28+
/// @see org.hibernate.annotations.NaturalIdClass
29+
///
30+
/// @implSpec Will trigger an [IllegalArgumentException] if the entity does
31+
/// not define a natural-id.
32+
NATURAL_ID
33+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ public interface Session extends SharedSessionContract, EntityManager {
583583
/// @param options The options to apply to the find operation. May contain [FindMultipleOption] values.
584584
///
585585
/// @apiNote For non-aggregated composite natural-ids, consider leveraging a [org.hibernate.annotations.NaturalIdClass].
586-
<T> List<T> findMultipleByNaturalId(Class<T> entityType, List<Object> naturalIds, FindOption... options);
586+
<T> List<T> findMultipleByNaturalId(Class<T> entityType, List<?> naturalIds, FindOption... options);
587587

588588
/// Find multiple entities by [natural-id][org.hibernate.annotations.NaturalId].
589589
///
@@ -592,7 +592,7 @@ public interface Session extends SharedSessionContract, EntityManager {
592592
/// @param options The options to apply to the find operation. May contain [FindMultipleOption] values.
593593
///
594594
/// @apiNote For non-aggregated composite natural-ids, consider leveraging a [org.hibernate.annotations.NaturalIdClass].
595-
List<Object> findMultipleByNaturalId(String entityName, List<Object> naturalIds, FindOption... options);
595+
List<Object> findMultipleByNaturalId(String entityName, List<?> naturalIds, FindOption... options);
596596

597597
/**
598598
* Return the persistent instances of the given entity class with the given identifiers

hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -917,12 +917,12 @@ public Object findByNaturalId(String entityName, Object naturalId, FindOption...
917917
}
918918

919919
@Override
920-
public <T> List<T> findMultipleByNaturalId(Class<T> entityType, List<Object> naturalIds, FindOption... options) {
920+
public <T> List<T> findMultipleByNaturalId(Class<T> entityType, List<?> naturalIds, FindOption... options) {
921921
return delegate.findMultipleByNaturalId( entityType, naturalIds, options );
922922
}
923923

924924
@Override
925-
public List<Object> findMultipleByNaturalId(String entityName, List<Object> naturalIds, FindOption... options) {
925+
public List<Object> findMultipleByNaturalId(String entityName, List<?> naturalIds, FindOption... options) {
926926
return delegate.findMultipleByNaturalId( entityName, naturalIds, options );
927927
}
928928

hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -831,12 +831,12 @@ public Object findByNaturalId(String entityName, Object naturalId, FindOption...
831831
}
832832

833833
@Override
834-
public <T> List<T> findMultipleByNaturalId(Class<T> entityType, List<Object> naturalIds, FindOption... options) {
834+
public <T> List<T> findMultipleByNaturalId(Class<T> entityType, List<?> naturalIds, FindOption... options) {
835835
return lazySession.get().findMultipleByNaturalId( entityType, naturalIds, options );
836836
}
837837

838838
@Override
839-
public List<Object> findMultipleByNaturalId(String entityName, List<Object> naturalIds, FindOption... options) {
839+
public List<Object> findMultipleByNaturalId(String entityName, List<?> naturalIds, FindOption... options) {
840840
return lazySession.get().findMultipleByNaturalId( entityName, naturalIds, options );
841841
}
842842

hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -927,12 +927,17 @@ public void load(Object object, Object id) {
927927
fireLoad( new LoadEvent( id, object, this, getReadOnlyFromLoadQueryInfluencers() ), LoadEventListener.RELOAD );
928928
}
929929

930-
private <T> void setMultiIdentifierLoadAccessOptions(FindOption[] options, MultiIdentifierLoadAccess<T> loadAccess) {
930+
private <T> boolean setMultiIdentifierLoadAccessOptions(FindOption[] options, MultiIdentifierLoadAccess<T> loadAccess) {
931931
CacheStoreMode storeMode = getCacheStoreMode();
932932
CacheRetrieveMode retrieveMode = getCacheRetrieveMode();
933933
LockOptions lockOptions = copySessionLockOptions();
934934
int batchSize = -1;
935935
for ( FindOption option : options ) {
936+
if ( option instanceof FindBy findBy ) {
937+
if ( findBy == FindBy.NATURAL_ID ) {
938+
return true;
939+
}
940+
}
936941
if ( option instanceof CacheStoreMode cacheStoreMode ) {
937942
storeMode = cacheStoreMode;
938943
}
@@ -980,12 +985,17 @@ else if ( option instanceof RemovalsMode ) {
980985
loadAccess.with( lockOptions )
981986
.with( interpretCacheMode( storeMode, retrieveMode ) )
982987
.withBatchSize( batchSize );
988+
989+
return false;
983990
}
984991

985992
@Override
986993
public <E> List<E> findMultiple(Class<E> entityType, List<?> ids, FindOption... options) {
987994
final var loadAccess = byMultipleIds( entityType );
988-
setMultiIdentifierLoadAccessOptions( options, loadAccess );
995+
final boolean isFindByNaturalId = setMultiIdentifierLoadAccessOptions( options, loadAccess );
996+
if ( isFindByNaturalId ) {
997+
return findMultipleByNaturalId( entityType, ids, options );
998+
}
989999
return loadAccess.multiLoad( ids );
9901000
}
9911001

@@ -999,7 +1009,10 @@ public <E> List<E> findMultiple(EntityGraph<E> entityGraph, List<?> ids, FindOpt
9991009
case POJO -> byMultipleIds( type.getJavaType() );
10001010
};
10011011
loadAccess.withLoadGraph( rootGraph );
1002-
setMultiIdentifierLoadAccessOptions( options, loadAccess );
1012+
final boolean isFindByNaturalId = setMultiIdentifierLoadAccessOptions( options, loadAccess );
1013+
if ( isFindByNaturalId ) {
1014+
throw new UnsupportedOperationException( "Find by natural-id with entity-graph is not supported" );
1015+
}
10031016
return loadAccess.multiLoad( ids );
10041017
}
10051018

@@ -2250,12 +2263,18 @@ protected static <T> void logIgnoringEntityNotFound(Class<T> entityClass, Object
22502263
}
22512264
}
22522265

2253-
private <T> void setLoadAccessOptions(FindOption[] options, IdentifierLoadAccessImpl<T> loadAccess) {
2266+
/// @return `true` if [FindBy#NATURAL_ID] was found
2267+
private <T> boolean setLoadAccessOptions(FindOption[] options, IdentifierLoadAccessImpl<T> loadAccess) {
22542268
CacheStoreMode storeMode = getCacheStoreMode();
22552269
CacheRetrieveMode retrieveMode = getCacheRetrieveMode();
22562270
LockOptions lockOptions = copySessionLockOptions();
22572271
for ( FindOption option : options ) {
2258-
if ( option instanceof CacheStoreMode cacheStoreMode ) {
2272+
if ( option instanceof FindBy findBy ) {
2273+
if ( findBy == FindBy.NATURAL_ID ) {
2274+
return true;
2275+
}
2276+
}
2277+
else if ( option instanceof CacheStoreMode cacheStoreMode ) {
22592278
storeMode = cacheStoreMode;
22602279
}
22612280
else if ( option instanceof CacheRetrieveMode cacheRetrieveMode ) {
@@ -2305,13 +2324,18 @@ else if ( option instanceof FindMultipleOption findMultipleOption ) {
23052324
}
23062325
}
23072326
loadAccess.with( lockOptions ).with( interpretCacheMode( storeMode, retrieveMode ) );
2327+
2328+
return false;
23082329
}
23092330

23102331
@Override
2311-
public <T> T find(Class<T> entityClass, Object primaryKey, FindOption... options) {
2332+
public <T> T find(Class<T> entityClass, Object key, FindOption... options) {
23122333
final IdentifierLoadAccessImpl<T> loadAccess = byId( entityClass );
2313-
setLoadAccessOptions( options, loadAccess );
2314-
return loadAccess.load( primaryKey );
2334+
final boolean isFindByNaturalId = setLoadAccessOptions( options, loadAccess );
2335+
if ( isFindByNaturalId ) {
2336+
return findByNaturalId( entityClass, key, options );
2337+
}
2338+
return loadAccess.load( key );
23152339
}
23162340

23172341
@Override
@@ -2323,7 +2347,10 @@ public <T> T find(EntityGraph<T> entityGraph, Object primaryKey, FindOption... o
23232347
case MAP -> byId( type.getTypeName() );
23242348
case POJO -> byId( type.getJavaType() );
23252349
};
2326-
setLoadAccessOptions( options, loadAccess );
2350+
final boolean isFindByNaturalId = setLoadAccessOptions( options, loadAccess );
2351+
if ( isFindByNaturalId ) {
2352+
throw new UnsupportedOperationException( "Find by natural-id with entity-graph is not supported" );
2353+
}
23272354
return loadAccess.withLoadGraph( graph ).load( primaryKey );
23282355
}
23292356

@@ -2395,7 +2422,10 @@ public Object find(String entityName, Object primaryKey) {
23952422
@Override
23962423
public Object find(String entityName, Object primaryKey, FindOption... options) {
23972424
final IdentifierLoadAccessImpl<?> loadAccess = byId( entityName );
2398-
setLoadAccessOptions( options, loadAccess );
2425+
final boolean isFindByNaturalId = setLoadAccessOptions( options, loadAccess );
2426+
if ( isFindByNaturalId ) {
2427+
return findByNaturalId( entityName, primaryKey, options );
2428+
}
23992429
return loadAccess.load( primaryKey );
24002430
}
24012431

@@ -2408,7 +2438,12 @@ public <T> T findByNaturalId(Class<T> entityType, Object naturalId, FindOption..
24082438

24092439
private <T> void setOptions(FindOption[] options, SimpleNaturalIdLoadAccessImpl<T> access) {
24102440
for ( FindOption option : options ) {
2411-
if ( option instanceof LockMode lockMode ) {
2441+
if ( option instanceof FindBy findBy ) {
2442+
if ( findBy == FindBy.ID ) {
2443+
throw new IllegalArgumentException( "Cannot use FindBy#ID with findByNaturalId" );
2444+
}
2445+
}
2446+
else if ( option instanceof LockMode lockMode ) {
24122447
access.with( lockMode );
24132448
}
24142449
else if ( option instanceof LockModeType lockModeType ) {
@@ -2437,15 +2472,20 @@ public Object findByNaturalId(String entityName, Object naturalId, FindOption...
24372472
}
24382473

24392474
@Override
2440-
public <T> List<T> findMultipleByNaturalId(Class<T> entityType, List<Object> naturalIds, FindOption... options) {
2475+
public <T> List<T> findMultipleByNaturalId(Class<T> entityType, List<?> naturalIds, FindOption... options) {
24412476
final NaturalIdMultiLoadAccessStandard<T> access = (NaturalIdMultiLoadAccessStandard<T>) byMultipleNaturalId( entityType );
24422477
setOptions( options, access );
24432478
return access.multiLoad( naturalIds );
24442479
}
24452480

24462481
private <T> void setOptions(FindOption[] options, NaturalIdMultiLoadAccessStandard<T> access) {
24472482
for ( FindOption option : options ) {
2448-
if ( option instanceof LockMode lockMode ) {
2483+
if ( option instanceof FindBy findBy ) {
2484+
if ( findBy == FindBy.ID ) {
2485+
throw new IllegalArgumentException( "Cannot use FindBy#ID with findMultipleByNaturalId" );
2486+
}
2487+
}
2488+
else if ( option instanceof LockMode lockMode ) {
24492489
access.with( lockMode );
24502490
}
24512491
else if ( option instanceof LockModeType lockModeType ) {
@@ -2479,7 +2519,7 @@ else if ( option instanceof OrderingMode orderingMode ) {
24792519
}
24802520

24812521
@Override
2482-
public List<Object> findMultipleByNaturalId(String entityName, List<Object> naturalIds, FindOption... options) {
2522+
public List<Object> findMultipleByNaturalId(String entityName, List<?> naturalIds, FindOption... options) {
24832523
final NaturalIdMultiLoadAccessStandard<Object> access = (NaturalIdMultiLoadAccessStandard<Object>) byMultipleNaturalId( entityName );
24842524
setOptions( options, access );
24852525
return access.multiLoad( naturalIds );

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,10 @@ public static <T> KeyClassNormalizer<T> create(
727727
naturalIdClassComponents,
728728
modelsContext
729729
);
730+
// todo (natural-id-class) : atm there is functionally no difference
731+
// between BasicAttributeMapperImpl and ToOneAttributeMapperImpl.
732+
// ideally we'd eventually support usage of the associated key entity's
733+
// id and then there would. see the note in ToOneAttributeMapperImpl#extractFrom
730734
final AttributeMapper<Object,T> attrMapper;
731735
if ( keyAttribute instanceof ToOneAttributeMapping ) {
732736
attrMapper = new ToOneAttributeMapperImpl<>( keyAttribute, extractor );
@@ -852,6 +856,12 @@ public record ToOneAttributeMapperImpl<T>(AttributeMapping entityAttribute, Gett
852856
@Override
853857
public Object extractFrom(T keyValue) {
854858
// todo (natural-id-class) : handle "key -> to-one" resolutions
859+
// this requires some contract changes though to pass Session
860+
// to be able to resolve key -> entity for the to-one.
861+
// +
862+
/// the other difficulty is handling "derived id" structures
863+
//
864+
// see `NaturalIdMapping#normalizeInput`
855865
return keyClassExtractor.get( keyValue );
856866
}
857867
}

0 commit comments

Comments
 (0)