more efficient loading by multiple @NaturalIds

This commit is contained in:
Gavin King 2021-02-23 18:35:54 +01:00 committed by Steve Ebersole
parent 725083b767
commit e368ac5bab
5 changed files with 199 additions and 17 deletions

View File

@ -7,7 +7,6 @@
package org.hibernate.event.internal; package org.hibernate.event.internal;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
@ -128,12 +127,10 @@ public class DefaultResolveNaturalIdEventListener
final Serializable pk; final Serializable pk;
EntityPersister persister = event.getEntityPersister(); EntityPersister persister = event.getEntityPersister();
LockOptions lockOptions = event.getLockOptions(); LockOptions lockOptions = event.getLockOptions();
if ( persister instanceof UniqueKeyLoadable if ( persister instanceof UniqueKeyLoadable) {
&& naturalIdValues.length==1 ) {
UniqueKeyLoadable rootPersister = (UniqueKeyLoadable) UniqueKeyLoadable rootPersister = (UniqueKeyLoadable)
persister.getFactory().getMetamodel().entityPersister( persister.getRootEntityName() ); persister.getFactory().getMetamodel().entityPersister( persister.getRootEntityName() );
Map.Entry<String, Object> e = event.getNaturalIdValues().entrySet().iterator().next(); Object entity = rootPersister.loadByNaturalId( naturalIdValues, lockOptions, session );
Object entity = rootPersister.loadByUniqueKey( e.getKey(), e.getValue(), lockOptions, session );
if ( entity == null ) { if ( entity == null ) {
pk = null; pk = null;
} }
@ -141,7 +138,7 @@ public class DefaultResolveNaturalIdEventListener
if ( !persister.isInstance(entity) ) { if ( !persister.isInstance(entity) ) {
throw new WrongClassException( throw new WrongClassException(
"loaded object was of wrong class " + entity.getClass(), "loaded object was of wrong class " + entity.getClass(),
e.getKey(), naturalIdValues,
persister.getEntityName() persister.getEntityName()
); );
} }

View File

@ -237,6 +237,15 @@ public final class ArrayHelper {
return true; return true;
} }
public static boolean[] negate(boolean[] valueNullness) {
boolean[] result = new boolean[valueNullness.length];
for (int i = 0; i < valueNullness.length; i++) {
result[i] = !valueNullness[i];
}
return result;
}
public static <T> void addAll(Collection<T> collection, T[] array) { public static <T> void addAll(Collection<T> collection, T[] array) {
collection.addAll( Arrays.asList( array ) ); collection.addAll( Arrays.asList( array ) );
} }

View File

@ -6,15 +6,28 @@
*/ */
package org.hibernate.loader.entity; package org.hibernate.loader.entity;
import org.hibernate.HibernateException;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.type.AbstractType;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/** /**
* Loads an entity instance using outerjoin fetching to fetch associated entities. * Loads an entity instance using outerjoin fetching to fetch associated entities.
* <br> * <br>
@ -97,7 +110,7 @@ public class EntityLoader extends AbstractEntityLoader {
loadQueryInfluencers loadQueryInfluencers
); );
initFromWalker( walker ); initFromWalker( walker );
this.compositeKeyManyToOneTargetIndices = walker.getCompositeKeyManyToOneTargetIndices(); compositeKeyManyToOneTargetIndices = walker.getCompositeKeyManyToOneTargetIndices();
postInstantiate(); postInstantiate();
batchLoader = batchSize > 1; batchLoader = batchSize > 1;
@ -126,7 +139,7 @@ public class EntityLoader extends AbstractEntityLoader {
loadQueryInfluencers loadQueryInfluencers
); );
initFromWalker( walker ); initFromWalker( walker );
this.compositeKeyManyToOneTargetIndices = walker.getCompositeKeyManyToOneTargetIndices(); compositeKeyManyToOneTargetIndices = walker.getCompositeKeyManyToOneTargetIndices();
postInstantiate(); postInstantiate();
batchLoader = batchSize > 1; batchLoader = batchSize > 1;
@ -140,6 +153,58 @@ public class EntityLoader extends AbstractEntityLoader {
} }
} }
public EntityLoader(
OuterJoinLoadable persister,
boolean[] valueNullness,
int batchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
super( persister, new NaturalIdType( persister, valueNullness ), factory, loadQueryInfluencers );
EntityJoinWalker walker = new EntityJoinWalker(
persister,
naturalIdColumns( valueNullness ),
batchSize,
lockOptions,
factory,
loadQueryInfluencers
) {
@Override
protected StringBuilder whereString(String alias, String[] columnNames, int batchSize) {
StringBuilder sql = super.whereString(alias, columnNames, batchSize);
for (String nullCol : naturalIdColumns( ArrayHelper.negate( valueNullness ) ) ) {
sql.append(" and ").append( getAlias() ).append('.').append(nullCol).append(" is null");
}
return sql;
}
};
initFromWalker( walker );
compositeKeyManyToOneTargetIndices = walker.getCompositeKeyManyToOneTargetIndices();
postInstantiate();
batchLoader = batchSize > 1;
if ( LOG.isDebugEnabled() ) {
LOG.debugf( "Static select for entity %s [%s:%s]: %s",
entityName,
lockOptions.getLockMode(),
lockOptions.getTimeOut(),
getSQLString() );
}
}
private String[] naturalIdColumns(boolean[] valueNullness) {
int i = 0;
List<String> columns = new ArrayList<>();
for ( int p : persister.getNaturalIdentifierProperties() ) {
if ( !valueNullness[i++] ) {
columns.addAll( Arrays.asList( persister.getPropertyColumnNames(p) ) );
}
}
return columns.toArray(ArrayHelper.EMPTY_STRING_ARRAY);
}
public Object loadByUniqueKey(SharedSessionContractImplementor session, Object key) { public Object loadByUniqueKey(SharedSessionContractImplementor session, Object key) {
return loadByUniqueKey( session, key, null ); return loadByUniqueKey( session, key, null );
} }
@ -157,4 +222,118 @@ public class EntityLoader extends AbstractEntityLoader {
public int[][] getCompositeKeyManyToOneTargetIndices() { public int[][] getCompositeKeyManyToOneTargetIndices() {
return compositeKeyManyToOneTargetIndices; return compositeKeyManyToOneTargetIndices;
} }
static class NaturalIdType extends AbstractType {
private OuterJoinLoadable persister;
private boolean[] valueNullness;
NaturalIdType(OuterJoinLoadable persister, boolean[] valueNullness) {
this.persister = persister;
this.valueNullness = valueNullness;
}
@Override
public int getColumnSpan(Mapping mapping) throws MappingException {
int span = 0;
int i = 0;
for ( int p : persister.getNaturalIdentifierProperties() ) {
if ( !valueNullness[i++] ) {
span += persister.getPropertyColumnNames(p).length;
}
}
return span;
}
@Override
public int[] sqlTypes(Mapping mapping) throws MappingException {
throw new UnsupportedOperationException();
}
@Override
public Size[] dictatedSizes(Mapping mapping) throws MappingException {
throw new UnsupportedOperationException();
}
@Override
public Size[] defaultSizes(Mapping mapping) throws MappingException {
throw new UnsupportedOperationException();
}
@Override
public Class getReturnedClass() {
throw new UnsupportedOperationException();
}
@Override
public boolean isDirty(Object oldState, Object currentState, boolean[] checkable, SharedSessionContractImplementor session)
throws HibernateException {
throw new UnsupportedOperationException();
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
throws HibernateException, SQLException {
throw new UnsupportedOperationException();
}
@Override
public Object nullSafeGet(ResultSet rs, String name, SharedSessionContractImplementor session, Object owner)
throws HibernateException, SQLException {
throw new UnsupportedOperationException();
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable, SharedSessionContractImplementor session)
throws HibernateException, SQLException {
throw new UnsupportedOperationException();
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
throws HibernateException, SQLException {
Object[] keys = (Object[]) value;
int i = 0;
for ( int p : persister.getNaturalIdentifierProperties() ) {
if ( !valueNullness[i] ) {
persister.getPropertyTypes()[p].nullSafeSet( st, keys[i], index++, session );
}
i++;
}
}
@Override
public String toLoggableString(Object value, SessionFactoryImplementor factory) {
return "natural id";
}
@Override
public String getName() {
throw new UnsupportedOperationException();
}
@Override
public Object deepCopy(Object value, SessionFactoryImplementor factory) {
throw new UnsupportedOperationException();
}
@Override
public boolean isMutable() {
throw new UnsupportedOperationException();
}
@Override
public Object resolve(Object value, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) {
throw new UnsupportedOperationException();
}
@Override
public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) {
throw new UnsupportedOperationException();
}
@Override
public boolean[] toColumnNullness(Object value, Mapping mapping) {
throw new UnsupportedOperationException();
}
}
} }

View File

@ -2481,21 +2481,19 @@ public abstract class AbstractEntityPersister
return getAppropriateUniqueKeyLoader( propertyName, session ).loadByUniqueKey( session, uniqueKey ); return getAppropriateUniqueKeyLoader( propertyName, session ).loadByUniqueKey( session, uniqueKey );
} }
public Object loadByUniqueKey( public Object loadByNaturalId(
String propertyName, Object[] naturalIdValues,
Object uniqueKey,
LockOptions lockOptions, LockOptions lockOptions,
SharedSessionContractImplementor session) throws HibernateException { SharedSessionContractImplementor session) throws HibernateException {
//TODO: cache this //TODO: cache this
return new EntityLoader( return new EntityLoader(
this, this,
propertyMapping.toColumns( propertyName ), determineValueNullness( naturalIdValues ),
propertyMapping.toType( propertyName ),
1, 1,
lockOptions, lockOptions,
getFactory(), getFactory(),
session.getLoadQueryInfluencers() session.getLoadQueryInfluencers()
).loadByUniqueKey( session, uniqueKey ); ).loadByUniqueKey( session, naturalIdValues );
} }
private EntityLoader getAppropriateUniqueKeyLoader(String propertyName, SharedSessionContractImplementor session) { private EntityLoader getAppropriateUniqueKeyLoader(String propertyName, SharedSessionContractImplementor session) {

View File

@ -25,9 +25,8 @@ public interface UniqueKeyLoadable extends Loadable {
/** /**
* Load an instance of the persistent class, by a natural id. * Load an instance of the persistent class, by a natural id.
*/ */
Object loadByUniqueKey( Object loadByNaturalId(
String propertyName, Object[] naturalIds,
Object uniqueKey,
LockOptions lockOptions, LockOptions lockOptions,
SharedSessionContractImplementor session); SharedSessionContractImplementor session);