HHH-13725: Implement ToOne Associations support

This commit is contained in:
Andrea Boriero 2019-11-22 19:05:43 +00:00
parent f1bf079122
commit 8a196bc0e5
8 changed files with 284 additions and 51 deletions

View File

@ -35,6 +35,7 @@ import org.hibernate.sql.ast.tree.from.TableReferenceCollector;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.results.internal.domain.entity.DelayedEntityFetchImpl;
import org.hibernate.sql.results.internal.domain.entity.EntityFetch;
import org.hibernate.sql.results.internal.domain.entity.SelectEntityFetchImpl;
import org.hibernate.sql.results.spi.DomainResult;
import org.hibernate.sql.results.spi.DomainResultCreationState;
import org.hibernate.sql.results.spi.Fetch;
@ -47,10 +48,12 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
implements EntityValuedModelPart, TableGroupJoinProducer {
private final String sqlAliasStem;
private final boolean isNullable;
private final boolean referringPrimaryKey;
final protected boolean unwrapProxy;
private final String referencedPropertyName;
private ForeignKeyDescriptor foreignKeyDescriptor;
private final String referencedPropertyName;
private final boolean referringPrimaryKey;
public SingularAssociationAttributeMapping(
String name,
@ -74,6 +77,7 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
this.isNullable = value.isNullable();
referencedPropertyName = value.getReferencedPropertyName();
referringPrimaryKey = value.isReferenceToPrimaryKey();
unwrapProxy = value.isUnwrapProxy();
}
public void setForeignKeyDescriptor(ForeignKeyDescriptor foreignKeyDescriptor) {
@ -144,7 +148,7 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
fetchParent,
this,
lockMode,
!selected,
true,
fetchablePath,
creationState
);
@ -160,14 +164,23 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
.createDomainResult( fetchablePath, lhsTableGroup, null, creationState );
}
if ( fetchTiming == FetchTiming.IMMEDIATE && !selected ) {
return new SelectEntityFetchImpl(
fetchParent,
this,
lockMode,
fetchablePath,
result
);
}
return new DelayedEntityFetchImpl(
fetchParent,
this,
lockMode,
isNullable,
fetchablePath,
result,
creationState
result
);
}
@ -280,4 +293,11 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
return false;
}
public boolean isNullable() {
return isNullable;
}
public boolean isUnwrapProxy() {
return unwrapProxy;
}
}

View File

@ -15,6 +15,7 @@ import org.hibernate.engine.profile.FetchProfile;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.loader.PropertyPath;
import org.hibernate.mapping.ToOne;
import org.hibernate.persister.collection.AbstractCollectionPersister;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
@ -119,6 +120,14 @@ public final class FetchStrategyHelper {
FetchStyle style,
AssociationType type,
SessionFactoryImplementor sessionFactory) {
if ( type instanceof ToOne ) {
if ( ( (ToOne) type ).isLazy() ) {
return FetchTiming.DELAYED;
}
else {
return FetchTiming.IMMEDIATE;
}
}
switch ( style ) {
case JOIN: {
return FetchTiming.IMMEDIATE;

View File

@ -229,7 +229,10 @@ public class StandardSqmSelectTranslator
// because it
assert getFromClauseIndex().getTableGroup( fetchablePath ) != null;
fetchTiming = FetchTiming.IMMEDIATE;
//
if ( fetchedJoin.isFetched() ) {
fetchTiming = FetchTiming.IMMEDIATE;
}
joined = true;
alias = fetchedJoin.getExplicitAlias();
lockMode = determineLockMode( alias );

View File

@ -26,7 +26,7 @@ import org.hibernate.sql.results.spi.Initializer;
*/
public class DelayedEntityFetchImpl extends AbstractEntityFecth {
private DomainResult result;
private final DomainResult result;
public DelayedEntityFetchImpl(
FetchParent fetchParent,
@ -34,8 +34,7 @@ public class DelayedEntityFetchImpl extends AbstractEntityFecth {
LockMode lockMode,
boolean nullable,
NavigablePath navigablePath,
DomainResult result,
DomainResultCreationState creationState) {
DomainResult result) {
super( fetchParent, fetchedAttribute, navigablePath, nullable, lockMode );
this.result = result;
}
@ -47,10 +46,7 @@ public class DelayedEntityFetchImpl extends AbstractEntityFecth {
AssemblerCreationState creationState) {
final SingularAssociationAttributeMapping fetchedAttribute = (SingularAssociationAttributeMapping) getFetchedMapping();
return new DelayedEntityFetchInitializer(
parentAccess,
getNavigablePath(),
fetchedAttribute.getMappedFetchStrategy(),
getLockMode(),
(EntityPersister) fetchedAttribute.getMappedTypeDescriptor(),
result.createResultAssembler( collector, creationState )
);

View File

@ -8,16 +8,12 @@ package org.hibernate.sql.results.internal.domain.entity;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.results.internal.domain.AbstractFetchParentAccess;
import org.hibernate.sql.results.spi.DomainResultAssembler;
import org.hibernate.sql.results.spi.EntityInitializer;
import org.hibernate.sql.results.spi.FetchParentAccess;
import org.hibernate.sql.results.spi.RowProcessingState;
/**
@ -26,31 +22,20 @@ import org.hibernate.sql.results.spi.RowProcessingState;
*/
public class DelayedEntityFetchInitializer extends AbstractFetchParentAccess implements EntityInitializer {
private final FetchParentAccess parentAccess;
private final NavigablePath navigablePath;
private FetchStrategy mappedFetchedStrategy;
private LockMode lockMode;
private final EntityPersister concreteDescriptor;
private final DomainResultAssembler fkValueAssembler;
private final DomainResultAssembler identifierAssembler;
private Object entityInstance;
private Object fkValue;
private Object identifier;
protected DelayedEntityFetchInitializer(
FetchParentAccess parentAccess,
NavigablePath fetchedNavigable,
FetchStrategy mappedFetchedStrategy,
LockMode lockMode,
EntityPersister concreteDescriptor,
DomainResultAssembler fkValueAssembler
) {
this.parentAccess = parentAccess;
DomainResultAssembler identifierAssembler) {
this.navigablePath = fetchedNavigable;
this.mappedFetchedStrategy = mappedFetchedStrategy;
this.lockMode = lockMode;
this.concreteDescriptor = concreteDescriptor;
this.fkValueAssembler = fkValueAssembler;
this.identifierAssembler = identifierAssembler;
}
@Override
@ -65,34 +50,32 @@ public class DelayedEntityFetchInitializer extends AbstractFetchParentAccess imp
@Override
public void resolveInstance(RowProcessingState rowProcessingState) {
fkValue = fkValueAssembler.assemble( rowProcessingState );
if ( entityInstance != null ) {
return;
}
identifier = identifierAssembler.assemble( rowProcessingState );
// todo (6.0) : technically the entity could be managed or cached already. who/what handles that?
// todo (6.0) : could also be getting loaded elsewhere (LoadingEntityEntry)
if ( fkValue == null ) {
if ( identifier == null ) {
// todo (6.0) : check this is the correct behaviour
entityInstance = null;
}
else {
if ( mappedFetchedStrategy.getTiming() != FetchTiming.IMMEDIATE ) {
if ( concreteDescriptor.hasProxy() ) {
entityInstance = concreteDescriptor.createProxy(
fkValue,
rowProcessingState.getSession()
);
}
else if ( concreteDescriptor
.getBytecodeEnhancementMetadata()
.isEnhancedForLazyLoading() ) {
entityInstance = concreteDescriptor.instantiate(
fkValue,
rowProcessingState.getSession()
);
}
if ( concreteDescriptor.hasProxy() ) {
entityInstance = concreteDescriptor.createProxy(
identifier,
rowProcessingState.getSession()
);
}
else {
entityInstance = rowProcessingState.getSession().immediateLoad( concreteDescriptor.getEntityName(), fkValue );
else if ( concreteDescriptor
.getBytecodeEnhancementMetadata()
.isEnhancedForLazyLoading() ) {
entityInstance = concreteDescriptor.instantiate(
identifier,
rowProcessingState.getSession()
);
}
notifyParentResolutionListeners( entityInstance );
@ -107,7 +90,7 @@ public class DelayedEntityFetchInitializer extends AbstractFetchParentAccess imp
@Override
public void finishUpRow(RowProcessingState rowProcessingState) {
entityInstance = null;
fkValue = null;
identifier = null;
clearParentResolutionListeners();
}
@ -136,4 +119,5 @@ public class DelayedEntityFetchInitializer extends AbstractFetchParentAccess imp
super.registerResolutionListener( listener );
}
}
}

View File

@ -0,0 +1,57 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.results.internal.domain.entity;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.metamodel.mapping.internal.SingularAssociationAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.results.spi.AssemblerCreationState;
import org.hibernate.sql.results.spi.DomainResult;
import org.hibernate.sql.results.spi.EntityInitializer;
import org.hibernate.sql.results.spi.FetchParent;
import org.hibernate.sql.results.spi.FetchParentAccess;
import org.hibernate.sql.results.spi.Initializer;
/**
* @author Andrea Boriero
*/
public class SelectEntityFetchImpl extends AbstractEntityFecth {
private final DomainResult result;
public SelectEntityFetchImpl(
FetchParent fetchParent,
SingularAssociationAttributeMapping fetchedAttribute,
LockMode lockMode,
NavigablePath navigablePath,
DomainResult result) {
super( fetchParent, fetchedAttribute, navigablePath, fetchedAttribute.isNullable(), lockMode );
this.result = result;
}
@Override
protected EntityInitializer getEntityInitializer(
FetchParentAccess parentAccess,
Consumer<Initializer> collector,
AssemblerCreationState creationState) {
final SingularAssociationAttributeMapping fetchedAttribute = (SingularAssociationAttributeMapping) getFetchedMapping();
return new SelectEntityInitializer(
getNavigablePath(),
(EntityPersister) fetchedAttribute.getMappedTypeDescriptor(),
result.createResultAssembler( collector, creationState ),
fetchedAttribute.isUnwrapProxy(),
fetchedAttribute.isNullable()
);
}
}

View File

@ -0,0 +1,121 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.results.internal.domain.entity;
import java.util.function.Consumer;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.results.internal.domain.AbstractFetchParentAccess;
import org.hibernate.sql.results.spi.DomainResultAssembler;
import org.hibernate.sql.results.spi.EntityInitializer;
import org.hibernate.sql.results.spi.RowProcessingState;
/**
* @author Andrea Boriero
*/
public class SelectEntityInitializer extends AbstractFetchParentAccess implements EntityInitializer {
private final NavigablePath navigablePath;
private final EntityPersister concreteDescriptor;
private final DomainResultAssembler identifierAssembler;
private final boolean unwrapProxy;
private final boolean nullable;
private Object entityInstance;
protected SelectEntityInitializer(
NavigablePath fetchedNavigable,
EntityPersister concreteDescriptor,
DomainResultAssembler identifierAssembler,
boolean unwrapProxy,
boolean nullable) {
this.navigablePath = fetchedNavigable;
this.concreteDescriptor = concreteDescriptor;
this.identifierAssembler = identifierAssembler;
this.unwrapProxy = unwrapProxy;
this.nullable = nullable;
}
@Override
public NavigablePath getNavigablePath() {
return navigablePath;
}
@Override
public void resolveKey(RowProcessingState rowProcessingState) {
// nothing to do
}
@Override
public void resolveInstance(RowProcessingState rowProcessingState) {
}
@Override
public void initializeInstance(RowProcessingState rowProcessingState) {
if ( entityInstance != null ) {
return;
}
final Object id = identifierAssembler.assemble( rowProcessingState );
if ( id == null ) {
return;
}
final String entityName = concreteDescriptor.getEntityName();
final SharedSessionContractImplementor session = rowProcessingState.getSession();
entityInstance = session.internalLoad(
entityName,
id,
false,
nullable
);
if ( entityInstance instanceof HibernateProxy ) {
final boolean isProxyUnwrapEnabled = unwrapProxy && concreteDescriptor.isInstrumented();
( (HibernateProxy) entityInstance ).getHibernateLazyInitializer().setUnwrap( isProxyUnwrapEnabled );
}
}
@Override
public void finishUpRow(RowProcessingState rowProcessingState) {
entityInstance = null;
clearParentResolutionListeners();
}
@Override
public EntityPersister getEntityDescriptor() {
return concreteDescriptor;
}
@Override
public Object getEntityInstance() {
return entityInstance;
}
@Override
public Object getParentKey() {
throw new NotYetImplementedFor6Exception( getClass() );
}
@Override
public void registerResolutionListener(Consumer<Object> listener) {
if ( entityInstance != null ) {
listener.accept( entityInstance );
}
else {
super.registerResolutionListener( listener );
}
}
}

View File

@ -103,6 +103,7 @@ public class ManyToOneTest {
AnotherSimpleEntity anotherSimpleEntity = otherEntity.getAnotherSimpleEntity();
assertTrue( Hibernate.isInitialized( anotherSimpleEntity ) );
assertThat( anotherSimpleEntity.getName(), is( "other" ) );
assertThat( statistics.getPrepareStatementCount(), is( 2L ) );
}
@ -118,6 +119,7 @@ public class ManyToOneTest {
OtherEntity otherEntity = session.
createQuery( "from OtherEntity o join o.simpleEntity", OtherEntity.class )
.uniqueResult();
// the eager association is null
assertThat( statistics.getPrepareStatementCount(), is( 1L ) );
assertThat( otherEntity.getName(), is( "Bar" ) );
@ -134,8 +136,49 @@ public class ManyToOneTest {
assertThat( statistics.getPrepareStatementCount(), is( 2L ) );
}
);
scope.inTransaction(
session -> {
OtherEntity otherEntity = session.
createQuery( "from OtherEntity", OtherEntity.class )
.uniqueResult();
AnotherSimpleEntity anotherSimpleEntity = new AnotherSimpleEntity();
anotherSimpleEntity.setId( 3 );
anotherSimpleEntity.setName( "other" );
session.save( anotherSimpleEntity );
otherEntity.setAnotherSimpleEntity( anotherSimpleEntity );
}
);
statistics.clear();
scope.inTransaction(
session -> {
OtherEntity otherEntity = session.
createQuery( "from OtherEntity o join o.simpleEntity", OtherEntity.class )
.uniqueResult();
// the eager association is not null so a second select is executed
assertThat( statistics.getPrepareStatementCount(), is( 2L ) );
assertThat( otherEntity.getName(), is( "Bar" ) );
SimpleEntity simpleEntity = otherEntity.getSimpleEntity();
assertFalse( Hibernate.isInitialized( simpleEntity ) );
assertThat( simpleEntity, notNullValue() );
assertThat( simpleEntity.getName(), is( "Fab" ) );
assertThat( statistics.getPrepareStatementCount(), is( 3L ) );
AnotherSimpleEntity anotherSimpleEntity = otherEntity.getAnotherSimpleEntity();
assertTrue( Hibernate.isInitialized( anotherSimpleEntity ) );
assertThat( anotherSimpleEntity.getName(), is( "other" ) );
assertThat( statistics.getPrepareStatementCount(), is( 3L ) );
}
);
}
@Test
public void testHQLSelectWithFetchJoin(SessionFactoryScope scope) {
StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();