Skip to content

Commit ef22641

Browse files
committed
HHH-19115 - implement ordered multiloading with natural ids
(also corrected a couple of typo's I came across) Signed-off-by: Jan Schatteman <[email protected]>
1 parent 5ae82c0 commit ef22641

File tree

8 files changed

+536
-96
lines changed

8 files changed

+536
-96
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.loader.ast.internal;
6+
7+
8+
import org.hibernate.LockOptions;
9+
import org.hibernate.engine.spi.EntityEntry;
10+
import org.hibernate.engine.spi.EntityKey;
11+
import org.hibernate.engine.spi.PersistenceContext;
12+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
13+
import org.hibernate.loader.ast.spi.MultiNaturalIdLoadOptions;
14+
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
15+
import org.hibernate.metamodel.mapping.EntityMappingType;
16+
17+
import java.util.ArrayList;
18+
import java.util.Collections;
19+
import java.util.List;
20+
21+
/**
22+
* @author Jan Schatteman
23+
*/
24+
public abstract class AbstractMultiNaturalIdLoader<E> implements MultiNaturalIdLoader<E> {
25+
private final EntityMappingType entityDescriptor;
26+
27+
protected MultiNaturalIdLoadOptions options;
28+
29+
public AbstractMultiNaturalIdLoader(EntityMappingType entityDescriptor) {
30+
this.entityDescriptor = entityDescriptor;
31+
}
32+
33+
@Override
34+
public <K> List<E> multiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options, SharedSessionContractImplementor session) {
35+
assert naturalIds != null;
36+
37+
this.options = options;
38+
39+
if ( naturalIds.length == 0 ) {
40+
return Collections.emptyList();
41+
}
42+
43+
return options.isOrderReturnEnabled()
44+
? performOrderedMultiLoad( naturalIds, options, session )
45+
: performUnorderedMultiLoad( naturalIds, options, session );
46+
}
47+
48+
private <K> List<E> performUnorderedMultiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options, SharedSessionContractImplementor session) {
49+
if ( MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) {
50+
MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.tracef( "Unordered MultiLoad Starting - `%s`", getEntityDescriptor().getEntityName() );
51+
}
52+
53+
return unorderedMultiLoad(
54+
naturalIds,
55+
session,
56+
options.getLockOptions() == null ? LockOptions.NONE : options.getLockOptions()
57+
);
58+
}
59+
60+
protected abstract <K> List<E> unorderedMultiLoad(K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions);
61+
62+
private <K> List<E> performOrderedMultiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options, SharedSessionContractImplementor session) {
63+
if ( MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) {
64+
MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.tracef( "Ordered MultiLoad Starting - `%s`", getEntityDescriptor().getEntityName() );
65+
}
66+
67+
return orderedMultiLoad(
68+
naturalIds,
69+
session,
70+
options.getLockOptions() == null ? LockOptions.NONE : options.getLockOptions()
71+
);
72+
}
73+
74+
protected <K> List<E> orderedMultiLoad( K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions ) {
75+
76+
unorderedMultiLoad( naturalIds, session, lockOptions );
77+
78+
return handleResults( naturalIds, session, lockOptions );
79+
}
80+
81+
protected <K> List<E> handleResults( K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions ) {
82+
List<E> results = new ArrayList<>(naturalIds.length);
83+
for ( int i = 0; i < naturalIds.length; i++ ) {
84+
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
85+
86+
Object id = persistenceContext.getNaturalIdResolutions().findCachedIdByNaturalId( naturalIds[i], getEntityDescriptor() );
87+
88+
// Id can be null if a non-existent natural id is requested
89+
Object entity = id == null ? null
90+
: persistenceContext.getEntity( new EntityKey( id, getEntityDescriptor().getEntityPersister() ) );
91+
if ( entity != null && !options.isReturnOfDeletedEntitiesEnabled() ) {
92+
// make sure it is not DELETED
93+
final EntityEntry entry = persistenceContext.getEntry( entity );
94+
if ( entry.getStatus().isDeletedOrGone() ) {
95+
// the entity is locally deleted, and the options ask that we not return such entities...
96+
entity = null;
97+
}
98+
else {
99+
entity = persistenceContext.proxyFor( entity );
100+
}
101+
}
102+
results.add( (E) entity );
103+
}
104+
105+
return results;
106+
}
107+
108+
@Override
109+
public EntityMappingType getLoadable() {
110+
return getEntityDescriptor();
111+
}
112+
113+
protected EntityMappingType getEntityDescriptor() {
114+
return entityDescriptor;
115+
}
116+
}

hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoaderArrayParam.java

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@
44
*/
55
package org.hibernate.loader.ast.internal;
66

7-
import java.util.Collections;
87
import java.util.List;
98

109
import org.hibernate.LockOptions;
1110
import org.hibernate.engine.spi.SessionFactoryImplementor;
1211
import org.hibernate.engine.spi.SharedSessionContractImplementor;
13-
import org.hibernate.loader.ast.spi.MultiNaturalIdLoadOptions;
14-
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
1512
import org.hibernate.loader.ast.spi.SqlArrayMultiKeyLoader;
1613
import org.hibernate.metamodel.mapping.EntityMappingType;
1714
import org.hibernate.metamodel.mapping.JdbcMapping;
@@ -27,52 +24,31 @@
2724
/**
2825
* Standard MultiNaturalIdLoader implementation
2926
*/
30-
public class MultiNaturalIdLoaderArrayParam<E> implements MultiNaturalIdLoader<E>, SqlArrayMultiKeyLoader {
31-
private final EntityMappingType entityDescriptor;
27+
public class MultiNaturalIdLoaderArrayParam<E> extends AbstractMultiNaturalIdLoader<E> implements SqlArrayMultiKeyLoader {
3228
private final Class<?> keyClass;
3329

3430
public MultiNaturalIdLoaderArrayParam(EntityMappingType entityDescriptor) {
35-
assert entityDescriptor.getNaturalIdMapping() instanceof SimpleNaturalIdMapping;
31+
super(entityDescriptor);
3632

37-
this.entityDescriptor = entityDescriptor;
33+
assert entityDescriptor.getNaturalIdMapping() instanceof SimpleNaturalIdMapping;
3834

3935
this.keyClass = entityDescriptor.getNaturalIdMapping().getJavaType().getJavaTypeClass();
4036
}
4137

42-
@Override
43-
public EntityMappingType getLoadable() {
44-
return entityDescriptor;
45-
}
46-
4738
protected SimpleNaturalIdMapping getNaturalIdMapping() {
48-
return (SimpleNaturalIdMapping) entityDescriptor.getNaturalIdMapping();
39+
return (SimpleNaturalIdMapping) getEntityDescriptor().getNaturalIdMapping();
4940
}
5041

5142
protected BasicAttributeMapping getNaturalIdAttribute() {
5243
return (BasicAttributeMapping) getNaturalIdMapping().asAttributeMapping();
5344
}
5445

5546
@Override
56-
public <K> List<E> multiLoad(K[] naturalIds, MultiNaturalIdLoadOptions loadOptions, SharedSessionContractImplementor session) {
57-
if ( naturalIds == null ) {
58-
throw new IllegalArgumentException( "`naturalIds` is null" );
59-
}
60-
61-
if ( naturalIds.length == 0 ) {
62-
return Collections.emptyList();
63-
}
64-
65-
if ( MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) {
66-
MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.tracef( "MultiNaturalIdLoaderArrayParam#multiLoadStarting - `%s`", entityDescriptor.getEntityName() );
67-
}
47+
public <K> List<E> unorderedMultiLoad( K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions ) {
6848

6949
final SessionFactoryImplementor sessionFactory = session.getFactory();
70-
naturalIds = LoaderHelper.normalizeKeys( naturalIds, getNaturalIdAttribute(), session, sessionFactory );
7150

72-
final LockOptions lockOptions =
73-
loadOptions.getLockOptions() == null
74-
? LockOptions.NONE
75-
: loadOptions.getLockOptions();
51+
naturalIds = LoaderHelper.normalizeKeys( naturalIds, getNaturalIdAttribute(), session, sessionFactory );
7652

7753
final JdbcMapping arrayJdbcMapping = MultiKeyLoadHelper.resolveArrayJdbcMapping(
7854
getNaturalIdMapping().getSingleJdbcMapping(),

hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoaderInPredicate.java

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,25 @@
44
*/
55
package org.hibernate.loader.ast.internal;
66

7-
import java.util.Collections;
87
import java.util.List;
98

109
import org.hibernate.LockOptions;
1110
import org.hibernate.engine.spi.SessionFactoryImplementor;
1211
import org.hibernate.engine.spi.SharedSessionContractImplementor;
13-
import org.hibernate.loader.ast.spi.MultiNaturalIdLoadOptions;
14-
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
1512
import org.hibernate.loader.ast.spi.SqlInPredicateMultiKeyLoader;
1613
import org.hibernate.metamodel.mapping.EntityMappingType;
17-
import org.hibernate.sql.results.LoadingLogger;
1814

1915
/**
2016
* MultiNaturalIdLoader implementation using SQL IN predicate to specify the ids
2117
*/
22-
public class MultiNaturalIdLoaderInPredicate<E> implements MultiNaturalIdLoader<E>, SqlInPredicateMultiKeyLoader {
23-
private final EntityMappingType entityDescriptor;
18+
public class MultiNaturalIdLoaderInPredicate<E> extends AbstractMultiNaturalIdLoader<E> implements SqlInPredicateMultiKeyLoader {
2419

2520
public MultiNaturalIdLoaderInPredicate(EntityMappingType entityDescriptor) {
26-
this.entityDescriptor = entityDescriptor;
21+
super(entityDescriptor);
2722
}
2823

2924
@Override
30-
public <K> List<E> multiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options, SharedSessionContractImplementor session) {
31-
if ( naturalIds == null ) {
32-
throw new IllegalArgumentException( "`naturalIds` is null" );
33-
}
34-
35-
if ( naturalIds.length == 0 ) {
36-
return Collections.emptyList();
37-
}
38-
39-
if ( LoadingLogger.LOGGER.isTraceEnabled() ) {
40-
LoadingLogger.LOGGER.tracef( "Starting multi natural-id loading for `%s`", entityDescriptor.getEntityName() );
41-
}
25+
public <K> List<E> unorderedMultiLoad(K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions) {
4226

4327
final SessionFactoryImplementor sessionFactory = session.getFactory();
4428

@@ -50,48 +34,28 @@ public <K> List<E> multiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options,
5034
maxBatchSize =
5135
session.getJdbcServices().getJdbcEnvironment().getDialect()
5236
.getMultiKeyLoadSizingStrategy().determineOptimalBatchLoadSize(
53-
entityDescriptor.getNaturalIdMapping().getJdbcTypeCount(),
37+
getEntityDescriptor().getNaturalIdMapping().getJdbcTypeCount(),
5438
naturalIds.length,
5539
sessionFactory.getSessionFactoryOptions().inClauseParameterPaddingEnabled()
5640
);
5741
}
5842

5943
final int batchSize = Math.min( maxBatchSize, naturalIds.length );
6044

61-
final LockOptions lockOptions =
62-
options.getLockOptions() == null
63-
? LockOptions.NONE
64-
: options.getLockOptions();
65-
6645
final MultiNaturalIdLoadingBatcher batcher = new MultiNaturalIdLoadingBatcher(
67-
entityDescriptor,
68-
entityDescriptor.getNaturalIdMapping(),
46+
getEntityDescriptor(),
47+
getEntityDescriptor().getNaturalIdMapping(),
6948
batchSize,
7049
(naturalId, s) -> {
7150
// `naturalId` here is the one passed in by the API as part of the values array
72-
// todo (6.0) : use this to help create the ordered results
73-
return entityDescriptor.getNaturalIdMapping().normalizeInput( naturalId );
51+
return getEntityDescriptor().getNaturalIdMapping().normalizeInput( naturalId );
7452
},
7553
session.getLoadQueryInfluencers(),
7654
lockOptions,
7755
sessionFactory
7856
);
7957

80-
final List<E> results = batcher.multiLoad( naturalIds, session );
81-
82-
if ( results.size() == 1 ) {
83-
return results;
84-
}
85-
86-
if ( options.isOrderReturnEnabled() ) {
87-
throw new UnsupportedOperationException( "Support for ordered loading by multiple natural-id values is not supported" );
88-
}
89-
90-
return results;
58+
return batcher.multiLoad( naturalIds, session );
9159
}
9260

93-
@Override
94-
public EntityMappingType getLoadable() {
95-
return entityDescriptor;
96-
}
9761
}

hibernate-core/src/main/java/org/hibernate/loader/internal/LoadAccessContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public interface LoadAccessContext {
2828
void checkOpenOrWaitingForAutoClose();
2929

3030
/**
31-
* Callback to pulse the transaction coo
31+
* Callback to pulse the transaction coordinator
3232
*/
3333
void pulseTransactionCoordinator();
3434
void delayedAfterCompletion();

hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiIdEntityLoadTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public void testBasicEntitySimpleLoad(SessionFactoryScope scope) {
4646

4747
@Test
4848
@JiraKey( "HHH-17201" )
49-
public void testSimpleEntityUnOrderedMultiLoad(SessionFactoryScope scope) {
49+
public void testSimpleEntityUnorderedMultiLoad(SessionFactoryScope scope) {
5050
scope.inTransaction(
5151
session -> {
5252
List<Integer> idList = List.of( 0, 1 );
@@ -122,7 +122,7 @@ public void testBasicEntityOrderedDeleteCheckLoad(SessionFactoryScope scope) {
122122
// ````
123123

124124
@Test
125-
public void testBasicEntityUnOrderedDeleteCheckLoad(SessionFactoryScope scope) {
125+
public void testBasicEntityUnorderedDeleteCheckLoad(SessionFactoryScope scope) {
126126

127127
// using un-ordered results
128128
scope.inTransaction(

hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiLoadLockingTest.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,18 @@
2121
import org.hibernate.annotations.CacheConcurrencyStrategy;
2222
import org.hibernate.annotations.NaturalId;
2323
import org.hibernate.cfg.AvailableSettings;
24-
import org.hibernate.dialect.CockroachDialect;
2524
import org.hibernate.dialect.Dialect;
26-
import org.hibernate.dialect.HSQLDialect;
2725
import org.hibernate.dialect.PostgreSQLDialect;
2826
import org.hibernate.dialect.PostgreSQLSqlAstTranslator;
2927
import org.hibernate.engine.spi.SessionFactoryImplementor;
3028
import org.hibernate.sql.ast.tree.Statement;
3129
import org.hibernate.testing.jdbc.SQLStatementInspector;
3230
import org.hibernate.testing.orm.junit.DomainModel;
33-
import org.hibernate.testing.orm.junit.FailureExpected;
3431
import org.hibernate.testing.orm.junit.JiraKey;
3532
import org.hibernate.testing.orm.junit.ServiceRegistry;
3633
import org.hibernate.testing.orm.junit.SessionFactory;
3734
import org.hibernate.testing.orm.junit.SessionFactoryScope;
3835
import org.hibernate.testing.orm.junit.Setting;
39-
import org.hibernate.testing.orm.junit.SkipForDialect;
40-
import org.hibernate.testing.orm.junit.SkipForDialectGroup;
4136
import org.junit.jupiter.api.AfterEach;
4237
import org.junit.jupiter.api.BeforeEach;
4338
import org.junit.jupiter.api.Test;
@@ -62,16 +57,6 @@
6257
}
6358
)
6459
@JiraKey(value = "HHH-18992")
65-
@FailureExpected(reason = "Ordered loading by multiple natural-id values is not yet supported", jiraKey = "HHH-19115")
66-
// TODO remove the SkipForDialectGroup when the @FailureExpected is removed
67-
@SkipForDialectGroup(
68-
// The tests don't actually fail for the dialects below, skipping them so that the non-occurring expected failure doesn't fail the Test case
69-
value = {
70-
@SkipForDialect(dialectClass = PostgreSQLDialect.class, matchSubTypes = true),
71-
@SkipForDialect(dialectClass = CockroachDialect.class, matchSubTypes = true),
72-
@SkipForDialect(dialectClass = HSQLDialect.class),
73-
}
74-
)
7560
public class MultiLoadLockingTest {
7661

7762
private SQLStatementInspector sqlStatementInspector;

0 commit comments

Comments
 (0)