Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.loader.ast.internal;


import org.hibernate.LockOptions;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoadOptions;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.metamodel.mapping.EntityMappingType;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* @author Jan Schatteman
*/
public abstract class AbstractMultiNaturalIdLoader<E> implements MultiNaturalIdLoader<E> {
private final EntityMappingType entityDescriptor;

protected MultiNaturalIdLoadOptions options;

public AbstractMultiNaturalIdLoader(EntityMappingType entityDescriptor) {
this.entityDescriptor = entityDescriptor;
}

@Override
public <K> List<E> multiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options, SharedSessionContractImplementor session) {
assert naturalIds != null;

this.options = options;

if ( naturalIds.length == 0 ) {
return Collections.emptyList();
}

return options.isOrderReturnEnabled()
? performOrderedMultiLoad( naturalIds, options, session )
: performUnorderedMultiLoad( naturalIds, options, session );
}

private <K> List<E> performUnorderedMultiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options, SharedSessionContractImplementor session) {
if ( MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) {
MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.tracef( "Unordered MultiLoad Starting - `%s`", getEntityDescriptor().getEntityName() );
}

return unorderedMultiLoad(
naturalIds,
session,
options.getLockOptions() == null ? LockOptions.NONE : options.getLockOptions()
);
}

protected abstract <K> List<E> unorderedMultiLoad(K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions);

private <K> List<E> performOrderedMultiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options, SharedSessionContractImplementor session) {
if ( MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) {
MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.tracef( "Ordered MultiLoad Starting - `%s`", getEntityDescriptor().getEntityName() );
}

return orderedMultiLoad(
naturalIds,
session,
options.getLockOptions() == null ? LockOptions.NONE : options.getLockOptions()
);
}

protected <K> List<E> orderedMultiLoad( K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions ) {

unorderedMultiLoad( naturalIds, session, lockOptions );

return handleResults( naturalIds, session, lockOptions );
}

protected <K> List<E> handleResults( K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions ) {

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'lockOptions' is never used.
List<E> results = new ArrayList<>(naturalIds.length);
for ( int i = 0; i < naturalIds.length; i++ ) {
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();

Object id = persistenceContext.getNaturalIdResolutions().findCachedIdByNaturalId( naturalIds[i], getEntityDescriptor() );

// Id can be null if a non-existent natural id is requested
Object entity = id == null ? null
: persistenceContext.getEntity( new EntityKey( id, getEntityDescriptor().getEntityPersister() ) );
if ( entity != null && !options.isReturnOfDeletedEntitiesEnabled() ) {
// make sure it is not DELETED
final EntityEntry entry = persistenceContext.getEntry( entity );
if ( entry.getStatus().isDeletedOrGone() ) {
// the entity is locally deleted, and the options ask that we not return such entities...
entity = null;
}
else {
entity = persistenceContext.proxyFor( entity );
}
}
results.add( (E) entity );
}

return results;
}

@Override
public EntityMappingType getLoadable() {
return getEntityDescriptor();
}

protected EntityMappingType getEntityDescriptor() {
return entityDescriptor;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@
*/
package org.hibernate.loader.ast.internal;

import java.util.Collections;
import java.util.List;

import org.hibernate.LockOptions;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoadOptions;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.loader.ast.spi.SqlArrayMultiKeyLoader;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
Expand All @@ -27,52 +24,31 @@
/**
* Standard MultiNaturalIdLoader implementation
*/
public class MultiNaturalIdLoaderArrayParam<E> implements MultiNaturalIdLoader<E>, SqlArrayMultiKeyLoader {
private final EntityMappingType entityDescriptor;
public class MultiNaturalIdLoaderArrayParam<E> extends AbstractMultiNaturalIdLoader<E> implements SqlArrayMultiKeyLoader {
private final Class<?> keyClass;

public MultiNaturalIdLoaderArrayParam(EntityMappingType entityDescriptor) {
assert entityDescriptor.getNaturalIdMapping() instanceof SimpleNaturalIdMapping;
super(entityDescriptor);

this.entityDescriptor = entityDescriptor;
assert entityDescriptor.getNaturalIdMapping() instanceof SimpleNaturalIdMapping;

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

@Override
public EntityMappingType getLoadable() {
return entityDescriptor;
}

protected SimpleNaturalIdMapping getNaturalIdMapping() {
return (SimpleNaturalIdMapping) entityDescriptor.getNaturalIdMapping();
return (SimpleNaturalIdMapping) getEntityDescriptor().getNaturalIdMapping();
}

protected BasicAttributeMapping getNaturalIdAttribute() {
return (BasicAttributeMapping) getNaturalIdMapping().asAttributeMapping();
}

@Override
public <K> List<E> multiLoad(K[] naturalIds, MultiNaturalIdLoadOptions loadOptions, SharedSessionContractImplementor session) {
if ( naturalIds == null ) {
throw new IllegalArgumentException( "`naturalIds` is null" );
}

if ( naturalIds.length == 0 ) {
return Collections.emptyList();
}

if ( MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) {
MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.tracef( "MultiNaturalIdLoaderArrayParam#multiLoadStarting - `%s`", entityDescriptor.getEntityName() );
}
public <K> List<E> unorderedMultiLoad( K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions ) {

final SessionFactoryImplementor sessionFactory = session.getFactory();
naturalIds = LoaderHelper.normalizeKeys( naturalIds, getNaturalIdAttribute(), session, sessionFactory );

final LockOptions lockOptions =
loadOptions.getLockOptions() == null
? LockOptions.NONE
: loadOptions.getLockOptions();
naturalIds = LoaderHelper.normalizeKeys( naturalIds, getNaturalIdAttribute(), session, sessionFactory );

final JdbcMapping arrayJdbcMapping = MultiKeyLoadHelper.resolveArrayJdbcMapping(
getNaturalIdMapping().getSingleJdbcMapping(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,25 @@
*/
package org.hibernate.loader.ast.internal;

import java.util.Collections;
import java.util.List;

import org.hibernate.LockOptions;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoadOptions;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.loader.ast.spi.SqlInPredicateMultiKeyLoader;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.sql.results.LoadingLogger;

/**
* MultiNaturalIdLoader implementation using SQL IN predicate to specify the ids
*/
public class MultiNaturalIdLoaderInPredicate<E> implements MultiNaturalIdLoader<E>, SqlInPredicateMultiKeyLoader {
private final EntityMappingType entityDescriptor;
public class MultiNaturalIdLoaderInPredicate<E> extends AbstractMultiNaturalIdLoader<E> implements SqlInPredicateMultiKeyLoader {

public MultiNaturalIdLoaderInPredicate(EntityMappingType entityDescriptor) {
this.entityDescriptor = entityDescriptor;
super(entityDescriptor);
}

@Override
public <K> List<E> multiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options, SharedSessionContractImplementor session) {
if ( naturalIds == null ) {
throw new IllegalArgumentException( "`naturalIds` is null" );
}

if ( naturalIds.length == 0 ) {
return Collections.emptyList();
}

if ( LoadingLogger.LOGGER.isTraceEnabled() ) {
LoadingLogger.LOGGER.tracef( "Starting multi natural-id loading for `%s`", entityDescriptor.getEntityName() );
}
public <K> List<E> unorderedMultiLoad(K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions) {

final SessionFactoryImplementor sessionFactory = session.getFactory();

Expand All @@ -50,48 +34,28 @@ public <K> List<E> multiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options,
maxBatchSize =
session.getJdbcServices().getJdbcEnvironment().getDialect()
.getMultiKeyLoadSizingStrategy().determineOptimalBatchLoadSize(
entityDescriptor.getNaturalIdMapping().getJdbcTypeCount(),
getEntityDescriptor().getNaturalIdMapping().getJdbcTypeCount(),
naturalIds.length,
sessionFactory.getSessionFactoryOptions().inClauseParameterPaddingEnabled()
);
}

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

final LockOptions lockOptions =
options.getLockOptions() == null
? LockOptions.NONE
: options.getLockOptions();

final MultiNaturalIdLoadingBatcher batcher = new MultiNaturalIdLoadingBatcher(
entityDescriptor,
entityDescriptor.getNaturalIdMapping(),
getEntityDescriptor(),
getEntityDescriptor().getNaturalIdMapping(),
batchSize,
(naturalId, s) -> {
// `naturalId` here is the one passed in by the API as part of the values array
// todo (6.0) : use this to help create the ordered results
return entityDescriptor.getNaturalIdMapping().normalizeInput( naturalId );
return getEntityDescriptor().getNaturalIdMapping().normalizeInput( naturalId );
},
session.getLoadQueryInfluencers(),
lockOptions,
sessionFactory
);

final List<E> results = batcher.multiLoad( naturalIds, session );

if ( results.size() == 1 ) {
return results;
}

if ( options.isOrderReturnEnabled() ) {
throw new UnsupportedOperationException( "Support for ordered loading by multiple natural-id values is not supported" );
}

return results;
return batcher.multiLoad( naturalIds, session );
}

@Override
public EntityMappingType getLoadable() {
return entityDescriptor;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public interface LoadAccessContext {
void checkOpenOrWaitingForAutoClose();

/**
* Callback to pulse the transaction coo
* Callback to pulse the transaction coordinator
*/
void pulseTransactionCoordinator();
void delayedAfterCompletion();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void testBasicEntitySimpleLoad(SessionFactoryScope scope) {

@Test
@JiraKey( "HHH-17201" )
public void testSimpleEntityUnOrderedMultiLoad(SessionFactoryScope scope) {
public void testSimpleEntityUnorderedMultiLoad(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
List<Integer> idList = List.of( 0, 1 );
Expand Down Expand Up @@ -122,7 +122,7 @@ public void testBasicEntityOrderedDeleteCheckLoad(SessionFactoryScope scope) {
// ````

@Test
public void testBasicEntityUnOrderedDeleteCheckLoad(SessionFactoryScope scope) {
public void testBasicEntityUnorderedDeleteCheckLoad(SessionFactoryScope scope) {

// using un-ordered results
scope.inTransaction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,18 @@
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.NaturalId;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.dialect.PostgreSQLSqlAstTranslator;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.hibernate.testing.orm.junit.SkipForDialectGroup;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -62,16 +57,6 @@
}
)
@JiraKey(value = "HHH-18992")
@FailureExpected(reason = "Ordered loading by multiple natural-id values is not yet supported", jiraKey = "HHH-19115")
// TODO remove the SkipForDialectGroup when the @FailureExpected is removed
@SkipForDialectGroup(
// 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
value = {
@SkipForDialect(dialectClass = PostgreSQLDialect.class, matchSubTypes = true),
@SkipForDialect(dialectClass = CockroachDialect.class, matchSubTypes = true),
@SkipForDialect(dialectClass = HSQLDialect.class),
}
)
public class MultiLoadLockingTest {

private SQLStatementInspector sqlStatementInspector;
Expand Down
Loading
Loading