Skip to content

Commit 70e476b

Browse files
committed
HHH-16383 - NaturalIdClass
1 parent e3245cb commit 70e476b

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
@@ -928,12 +928,17 @@ public void load(Object object, Object id) {
928928
fireLoad( new LoadEvent( id, object, this, getReadOnlyFromLoadQueryInfluencers() ), LoadEventListener.RELOAD );
929929
}
930930

931-
private <T> void setMultiIdentifierLoadAccessOptions(FindOption[] options, MultiIdentifierLoadAccess<T> loadAccess) {
931+
private <T> boolean setMultiIdentifierLoadAccessOptions(FindOption[] options, MultiIdentifierLoadAccess<T> loadAccess) {
932932
CacheStoreMode storeMode = getCacheStoreMode();
933933
CacheRetrieveMode retrieveMode = getCacheRetrieveMode();
934934
LockOptions lockOptions = copySessionLockOptions();
935935
int batchSize = -1;
936936
for ( FindOption option : options ) {
937+
if ( option instanceof FindBy findBy ) {
938+
if ( findBy == FindBy.NATURAL_ID ) {
939+
return true;
940+
}
941+
}
937942
if ( option instanceof CacheStoreMode cacheStoreMode ) {
938943
storeMode = cacheStoreMode;
939944
}
@@ -981,12 +986,17 @@ else if ( option instanceof RemovalsMode ) {
981986
loadAccess.with( lockOptions )
982987
.with( interpretCacheMode( storeMode, retrieveMode ) )
983988
.withBatchSize( batchSize );
989+
990+
return false;
984991
}
985992

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

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

@@ -2251,12 +2264,18 @@ protected static <T> void logIgnoringEntityNotFound(Class<T> entityClass, Object
22512264
}
22522265
}
22532266

2254-
private <T> void setLoadAccessOptions(FindOption[] options, IdentifierLoadAccessImpl<T> loadAccess) {
2267+
/// @return `true` if [FindBy#NATURAL_ID] was found
2268+
private <T> boolean setLoadAccessOptions(FindOption[] options, IdentifierLoadAccessImpl<T> loadAccess) {
22552269
CacheStoreMode storeMode = getCacheStoreMode();
22562270
CacheRetrieveMode retrieveMode = getCacheRetrieveMode();
22572271
LockOptions lockOptions = copySessionLockOptions();
22582272
for ( FindOption option : options ) {
2259-
if ( option instanceof CacheStoreMode cacheStoreMode ) {
2273+
if ( option instanceof FindBy findBy ) {
2274+
if ( findBy == FindBy.NATURAL_ID ) {
2275+
return true;
2276+
}
2277+
}
2278+
else if ( option instanceof CacheStoreMode cacheStoreMode ) {
22602279
storeMode = cacheStoreMode;
22612280
}
22622281
else if ( option instanceof CacheRetrieveMode cacheRetrieveMode ) {
@@ -2306,13 +2325,18 @@ else if ( option instanceof FindMultipleOption findMultipleOption ) {
23062325
}
23072326
}
23082327
loadAccess.with( lockOptions ).with( interpretCacheMode( storeMode, retrieveMode ) );
2328+
2329+
return false;
23092330
}
23102331

23112332
@Override
2312-
public <T> T find(Class<T> entityClass, Object primaryKey, FindOption... options) {
2333+
public <T> T find(Class<T> entityClass, Object key, FindOption... options) {
23132334
final IdentifierLoadAccessImpl<T> loadAccess = byId( entityClass );
2314-
setLoadAccessOptions( options, loadAccess );
2315-
return loadAccess.load( primaryKey );
2335+
final boolean isFindByNaturalId = setLoadAccessOptions( options, loadAccess );
2336+
if ( isFindByNaturalId ) {
2337+
return findByNaturalId( entityClass, key, options );
2338+
}
2339+
return loadAccess.load( key );
23162340
}
23172341

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

@@ -2396,7 +2423,10 @@ public Object find(String entityName, Object primaryKey) {
23962423
@Override
23972424
public Object find(String entityName, Object primaryKey, FindOption... options) {
23982425
final IdentifierLoadAccessImpl<?> loadAccess = byId( entityName );
2399-
setLoadAccessOptions( options, loadAccess );
2426+
final boolean isFindByNaturalId = setLoadAccessOptions( options, loadAccess );
2427+
if ( isFindByNaturalId ) {
2428+
return findByNaturalId( entityName, primaryKey, options );
2429+
}
24002430
return loadAccess.load( primaryKey );
24012431
}
24022432

@@ -2409,7 +2439,12 @@ public <T> T findByNaturalId(Class<T> entityType, Object naturalId, FindOption..
24092439

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

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

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

24822522
@Override
2483-
public List<Object> findMultipleByNaturalId(String entityName, List<Object> naturalIds, FindOption... options) {
2523+
public List<Object> findMultipleByNaturalId(String entityName, List<?> naturalIds, FindOption... options) {
24842524
final NaturalIdMultiLoadAccessStandard<Object> access = (NaturalIdMultiLoadAccessStandard<Object>) byMultipleNaturalId( entityName );
24852525
setOptions( options, access );
24862526
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)