HHH-17818 Introduce new `@ConcreteProxy` annotation
Also, preserve laziness for optional + ConcreteType associations
This commit is contained in:
parent
2bc78d50b0
commit
84cb94b990
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Annotating {@link ConcreteProxy} on the root entity class of an inheritance hierarchy
|
||||
* will allow types of that hierarchy to always produce proxies that resolve to the concrete
|
||||
* subtype class. This means both {@linkplain jakarta.persistence.FetchType#LAZY lazy associations}
|
||||
* and {@linkplain org.hibernate.Session#getReference plain references} can safely be used
|
||||
* with {@code instanceof} checks and type-casts.
|
||||
* <p>
|
||||
* Note that the table(s) of an entity <strong>might need to be accessed</strong> to
|
||||
* determine the concrete proxy type:
|
||||
* <ul>
|
||||
* <li>
|
||||
* With {@linkplain jakarta.persistence.InheritanceType#SINGLE_TABLE single table} inheritance,
|
||||
* the {@linkplain jakarta.persistence.DiscriminatorColumn discriminator column} value
|
||||
* will be {@code left join}ed when fetching associations or simply read from the entity
|
||||
* table when getting references.
|
||||
* </li>
|
||||
* <li>
|
||||
* When using {@linkplain jakarta.persistence.InheritanceType#JOINED joined} inheritance,
|
||||
* all subtype tables will need to be {@code left join}ed to determine the concrete type.
|
||||
* Note however that when using an explicit {@linkplain jakarta.persistence.DiscriminatorColumn
|
||||
* discriminator column}, the behavior is the same as for single-table inheritance.
|
||||
* </li>
|
||||
* <li>
|
||||
* Finally, for {@linkplain jakarta.persistence.InheritanceType#TABLE_PER_CLASS table-per-class}
|
||||
* inheritance, all subtype tables will need to be (union) queried to determine the concrete type.
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Marco Belladelli
|
||||
* @since 6.6
|
||||
*/
|
||||
@Target( TYPE )
|
||||
@Retention( RUNTIME )
|
||||
@Incubating
|
||||
public @interface ConcreteProxy {
|
||||
}
|
|
@ -29,6 +29,7 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
|
|||
import org.hibernate.annotations.CacheLayout;
|
||||
import org.hibernate.annotations.Check;
|
||||
import org.hibernate.annotations.Checks;
|
||||
import org.hibernate.annotations.ConcreteProxy;
|
||||
import org.hibernate.annotations.DiscriminatorFormula;
|
||||
import org.hibernate.annotations.DynamicInsert;
|
||||
import org.hibernate.annotations.DynamicUpdate;
|
||||
|
@ -1303,6 +1304,7 @@ public class EntityBinder {
|
|||
bindOptimisticLocking();
|
||||
bindPolymorphism();
|
||||
bindProxy();
|
||||
bindConcreteProxy();
|
||||
bindWhere();
|
||||
bindCache();
|
||||
bindNaturalIdCache();
|
||||
|
@ -1591,6 +1593,17 @@ public class EntityBinder {
|
|||
return isDefault( proxyClass, context ) ? annotatedClass : proxyClass;
|
||||
}
|
||||
|
||||
public void bindConcreteProxy() {
|
||||
final ConcreteProxy concreteProxy = annotatedClass.getAnnotation( ConcreteProxy.class );
|
||||
if ( concreteProxy != null ) {
|
||||
if ( persistentClass.getSuperclass() != null ) {
|
||||
throw new AnnotationException( "Entity class '" + persistentClass.getClassName()
|
||||
+ "' is annotated '@ConcreteProxy' but it is not the root of the entity inheritance hierarchy" );
|
||||
}
|
||||
persistentClass.getRootClass().setConcreteProxy( true );
|
||||
}
|
||||
}
|
||||
|
||||
public void bindWhere() {
|
||||
final Where where = getOverridableAnnotation( annotatedClass, Where.class, context );
|
||||
if ( where != null ) {
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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.loader.ast.internal;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.ObjectNotFoundException;
|
||||
import org.hibernate.WrongClassException;
|
||||
import org.hibernate.annotations.ConcreteProxy;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
|
||||
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
||||
import org.hibernate.sql.exec.internal.BaseExecutionContext;
|
||||
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
|
||||
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||
import org.hibernate.sql.exec.spi.JdbcParametersList;
|
||||
import org.hibernate.sql.results.internal.RowTransformerStandardImpl;
|
||||
import org.hibernate.sql.results.spi.ListResultsConsumer;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
/**
|
||||
* Utility class that caches the SQL AST needed to read the discriminator value
|
||||
* associated with the provided {@link EntityPersister} and returns the
|
||||
* resolved concrete entity type.
|
||||
*
|
||||
* @author Marco Belladelli
|
||||
* @see ConcreteProxy
|
||||
*/
|
||||
public class EntityConcreteTypeLoader {
|
||||
private final EntityMappingType entityDescriptor;
|
||||
private final SelectStatement sqlSelect;
|
||||
private final JdbcParametersList jdbcParameters;
|
||||
|
||||
public EntityConcreteTypeLoader(EntityMappingType entityDescriptor, SessionFactoryImplementor sessionFactory) {
|
||||
this.entityDescriptor = entityDescriptor;
|
||||
final EntityDiscriminatorMapping discriminatorMapping = entityDescriptor.getDiscriminatorMapping();
|
||||
final JdbcParametersList.Builder jdbcParametersBuilder = JdbcParametersList.newBuilder();
|
||||
sqlSelect = LoaderSelectBuilder.createSelect(
|
||||
entityDescriptor,
|
||||
singletonList( discriminatorMapping ),
|
||||
entityDescriptor.getIdentifierMapping(),
|
||||
null,
|
||||
1,
|
||||
new LoadQueryInfluencers( sessionFactory ),
|
||||
LockOptions.NONE,
|
||||
jdbcParametersBuilder::add,
|
||||
sessionFactory
|
||||
);
|
||||
jdbcParameters = jdbcParametersBuilder.build();
|
||||
}
|
||||
|
||||
public EntityMappingType getConcreteType(Object id, SharedSessionContractImplementor session) {
|
||||
final SessionFactoryImplementor sessionFactory = session.getSessionFactory();
|
||||
final SqlAstTranslatorFactory sqlAstTranslatorFactory = sessionFactory.getJdbcServices()
|
||||
.getJdbcEnvironment()
|
||||
.getSqlAstTranslatorFactory();
|
||||
|
||||
final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
|
||||
int offset = jdbcParamBindings.registerParametersForEachJdbcValue(
|
||||
id,
|
||||
entityDescriptor.getIdentifierMapping(),
|
||||
jdbcParameters,
|
||||
session
|
||||
);
|
||||
assert offset == jdbcParameters.size();
|
||||
|
||||
final JdbcOperationQuerySelect jdbcSelect =
|
||||
sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlSelect )
|
||||
.translate( jdbcParamBindings, QueryOptions.NONE );
|
||||
final List<Object> results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
|
||||
jdbcSelect,
|
||||
jdbcParamBindings,
|
||||
new BaseExecutionContext( session ),
|
||||
RowTransformerStandardImpl.instance(),
|
||||
ListResultsConsumer.UniqueSemantic.NONE
|
||||
);
|
||||
|
||||
if ( results.isEmpty() ) {
|
||||
throw new ObjectNotFoundException( entityDescriptor.getEntityName(), id );
|
||||
}
|
||||
else {
|
||||
assert results.size() == 1;
|
||||
final Object result = results.get( 0 );
|
||||
final MappingMetamodelImplementor mappingMetamodel = sessionFactory.getRuntimeMetamodels()
|
||||
.getMappingMetamodel();
|
||||
final EntityPersister concreteType = result instanceof Class<?>
|
||||
? mappingMetamodel.getEntityDescriptor( (Class<?>) result )
|
||||
: mappingMetamodel.getEntityDescriptor( (String) result );
|
||||
if ( !concreteType.isTypeOrSuperType( entityDescriptor ) ) {
|
||||
throw new WrongClassException(
|
||||
concreteType.getEntityName(),
|
||||
id,
|
||||
entityDescriptor.getEntityName(),
|
||||
result
|
||||
);
|
||||
}
|
||||
return concreteType;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ import org.hibernate.event.spi.LoadEventListener;
|
|||
import org.hibernate.graph.GraphSemantic;
|
||||
import org.hibernate.graph.RootGraph;
|
||||
import org.hibernate.graph.spi.RootGraphImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.proxy.LazyInitializer;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
@ -128,16 +129,16 @@ public class IdentifierLoadAccessImpl<T> implements IdentifierLoadAccess<T>, Jav
|
|||
protected T doGetReference(Object id) {
|
||||
final SessionImplementor session = context.getSession();
|
||||
final SessionFactoryImplementor factory = session.getFactory();
|
||||
final EntityMappingType concreteType = entityPersister.resolveConcreteProxyTypeForId( id, session );
|
||||
return (T) getReference(
|
||||
coerceId( id, factory ),
|
||||
session.asEventSource(),
|
||||
factory,
|
||||
entityPersister.getEntityName(),
|
||||
concreteType.getEntityName(),
|
||||
isReadOnly( session )
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private Boolean isReadOnly(SessionImplementor session) {
|
||||
return readOnly != null
|
||||
? readOnly
|
||||
|
|
|
@ -415,6 +415,8 @@ public abstract class PersistentClass implements IdentifiableTypeClass, Attribut
|
|||
this.lazy = lazy;
|
||||
}
|
||||
|
||||
public abstract boolean isConcreteProxy();
|
||||
|
||||
public abstract boolean hasEmbeddedIdentifier();
|
||||
|
||||
public abstract Class<? extends EntityPersister> getEntityPersisterClass();
|
||||
|
|
|
@ -51,6 +51,7 @@ public class RootClass extends PersistentClass implements TableOwner, SoftDeleta
|
|||
private boolean explicitPolymorphism;
|
||||
private Class<? extends EntityPersister> entityPersisterClass;
|
||||
private boolean forceDiscriminator;
|
||||
private boolean concreteProxy;
|
||||
private String where;
|
||||
private Table table;
|
||||
private boolean discriminatorInsertable = true;
|
||||
|
@ -262,6 +263,15 @@ public class RootClass extends PersistentClass implements TableOwner, SoftDeleta
|
|||
this.forceDiscriminator = forceDiscriminator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConcreteProxy() {
|
||||
return concreteProxy;
|
||||
}
|
||||
|
||||
public void setConcreteProxy(boolean concreteProxy) {
|
||||
this.concreteProxy = concreteProxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWhere() {
|
||||
return where;
|
||||
|
|
|
@ -203,6 +203,11 @@ public class Subclass extends PersistentClass {
|
|||
return getSuperclass().isExplicitPolymorphism();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConcreteProxy() {
|
||||
return getRootClass().isConcreteProxy();
|
||||
}
|
||||
|
||||
public void setSuperclass(PersistentClass superclass) {
|
||||
this.superclass = superclass;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,9 @@ import java.util.function.Consumer;
|
|||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.Filter;
|
||||
import org.hibernate.Incubating;
|
||||
import org.hibernate.Internal;
|
||||
import org.hibernate.annotations.ConcreteProxy;
|
||||
import org.hibernate.boot.jaxb.mapping.JaxbEntity;
|
||||
import org.hibernate.engine.OptimisticLockStyle;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
|
@ -243,7 +245,6 @@ public interface EntityMappingType
|
|||
return getDiscriminatorValue().toString();
|
||||
}
|
||||
|
||||
|
||||
default EntityMappingType getRootEntityDescriptor() {
|
||||
final EntityMappingType superMappingType = getSuperMappingType();
|
||||
if ( superMappingType == null ) {
|
||||
|
@ -326,6 +327,32 @@ public interface EntityMappingType
|
|||
*/
|
||||
EntityDiscriminatorMapping getDiscriminatorMapping();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this entity type's hierarchy is configured to return
|
||||
* {@linkplain ConcreteProxy concrete-typed} proxies.
|
||||
*
|
||||
* @see ConcreteProxy
|
||||
* @since 6.6
|
||||
*/
|
||||
@Incubating
|
||||
default boolean isConcreteProxy() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this entity is configured to return {@linkplain ConcreteProxy concrete-typed}
|
||||
* proxies, this method queries the entity table(s) do determine the concrete entity type
|
||||
* associated with the provided id and returns its persister. Otherwise, this method
|
||||
* simply returns this entity persister.
|
||||
*
|
||||
* @see #isConcreteProxy()
|
||||
* @since 6.6
|
||||
*/
|
||||
@Incubating
|
||||
default EntityMappingType resolveConcreteProxyTypeForId(Object id, SharedSessionContractImplementor session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping details for the entity's version when using the
|
||||
* {@linkplain OptimisticLockStyle#VERSION version strategy}.
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.util.LinkedHashMap;
|
|||
import org.hibernate.engine.FetchTiming;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.DiscriminatorType;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
|
||||
import org.hibernate.persister.entity.JoinedSubclassEntityPersister;
|
||||
|
@ -28,6 +29,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
|
|||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.predicate.NullnessPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.FetchParent;
|
||||
import org.hibernate.sql.results.graph.basic.BasicFetch;
|
||||
|
@ -76,6 +78,17 @@ public class CaseStatementDiscriminatorMappingImpl extends AbstractDiscriminator
|
|||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings( "rawtypes" )
|
||||
@Override
|
||||
public DomainResult createDomainResult(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup tableGroup,
|
||||
String resultVariable,
|
||||
DomainResultCreationState creationState) {
|
||||
resolveSubTypeTableReferences( tableGroup, navigablePath );
|
||||
return super.createDomainResult( navigablePath, tableGroup, resultVariable, creationState );
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicFetch<?> generateFetch(
|
||||
FetchParent fetchParent,
|
||||
|
@ -88,18 +101,23 @@ public class CaseStatementDiscriminatorMappingImpl extends AbstractDiscriminator
|
|||
final TableGroup tableGroup = sqlAstCreationState.getFromClauseAccess().getTableGroup(
|
||||
fetchParent.getNavigablePath()
|
||||
);
|
||||
// Since the expression is lazy, based on the available table reference joins,
|
||||
// we need to force the initialization in case this is a fetch
|
||||
tableDiscriminatorDetailsMap.forEach(
|
||||
(tableName, tableDiscriminatorDetails) -> tableGroup.getTableReference(
|
||||
fetchablePath,
|
||||
tableName,
|
||||
true
|
||||
)
|
||||
);
|
||||
resolveSubTypeTableReferences( tableGroup, fetchablePath );
|
||||
return super.generateFetch( fetchParent, fetchablePath, fetchTiming, selected, resultVariable, creationState );
|
||||
}
|
||||
|
||||
private void resolveSubTypeTableReferences(TableGroup tableGroup, NavigablePath navigablePath) {
|
||||
final EntityMappingType entityDescriptor = (EntityMappingType) tableGroup.getModelPart().getPartMappingType();
|
||||
// Since the expression is lazy, based on the available table reference joins,
|
||||
// we need to force the initialization in case this is selected
|
||||
for ( EntityMappingType subMappingType : entityDescriptor.getSubMappingTypes() ) {
|
||||
tableGroup.getTableReference(
|
||||
navigablePath,
|
||||
subMappingType.getMappedTableDetails().getTableName(),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression resolveSqlExpression(
|
||||
NavigablePath navigablePath,
|
||||
|
|
|
@ -1071,7 +1071,8 @@ public class ToOneAttributeMapping
|
|||
fetchParent,
|
||||
isSelectByUniqueKey( sideNature ),
|
||||
parentNavigablePath,
|
||||
foreignKeyDomainResult
|
||||
foreignKeyDomainResult,
|
||||
creationState
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
@ -1357,12 +1358,17 @@ public class ToOneAttributeMapping
|
|||
);
|
||||
}
|
||||
|
||||
if ( entityMappingType.isConcreteProxy() && sideNature == ForeignKeyDescriptor.Nature.TARGET ) {
|
||||
createTableGroupForDelayedFetch( fetchablePath, parentTableGroup, null, creationState );
|
||||
}
|
||||
|
||||
return buildEntityDelayedFetch(
|
||||
fetchParent,
|
||||
this,
|
||||
fetchablePath,
|
||||
domainResult,
|
||||
isSelectByUniqueKey( sideNature )
|
||||
isSelectByUniqueKey( sideNature ),
|
||||
creationState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1375,8 +1381,16 @@ public class ToOneAttributeMapping
|
|||
ToOneAttributeMapping fetchedAttribute,
|
||||
NavigablePath navigablePath,
|
||||
DomainResult<?> keyResult,
|
||||
boolean selectByUniqueKey) {
|
||||
return new EntityDelayedFetchImpl( fetchParent, fetchedAttribute, navigablePath, keyResult, selectByUniqueKey );
|
||||
boolean selectByUniqueKey,
|
||||
DomainResultCreationState creationState) {
|
||||
return new EntityDelayedFetchImpl(
|
||||
fetchParent,
|
||||
fetchedAttribute,
|
||||
navigablePath,
|
||||
keyResult,
|
||||
selectByUniqueKey,
|
||||
creationState
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1641,13 +1655,7 @@ public class ToOneAttributeMapping
|
|||
}
|
||||
final boolean selectByUniqueKey = isSelectByUniqueKey( side );
|
||||
|
||||
// Consider all associations annotated with @NotFound as EAGER
|
||||
// and LAZY one-to-one that are not instrumented and not optional
|
||||
if ( fetchTiming == FetchTiming.IMMEDIATE
|
||||
|| hasNotFoundAction()
|
||||
|| getAssociatedEntityMappingType().getSoftDeleteMapping() != null
|
||||
|| ( !entityMappingType.getEntityPersister().isInstrumented()
|
||||
&& cardinality == Cardinality.ONE_TO_ONE && isOptional ) ) {
|
||||
if ( needsImmediateFetch( fetchTiming ) ) {
|
||||
return buildEntityFetchSelect(
|
||||
fetchParent,
|
||||
this,
|
||||
|
@ -1658,15 +1666,39 @@ public class ToOneAttributeMapping
|
|||
);
|
||||
}
|
||||
|
||||
if ( entityMappingType.isConcreteProxy() && sideNature == ForeignKeyDescriptor.Nature.TARGET ) {
|
||||
createTableGroupForDelayedFetch( fetchablePath, parentTableGroup, null, creationState );
|
||||
}
|
||||
|
||||
return buildEntityDelayedFetch(
|
||||
fetchParent,
|
||||
this,
|
||||
fetchablePath,
|
||||
keyResult,
|
||||
selectByUniqueKey
|
||||
selectByUniqueKey,
|
||||
creationState
|
||||
);
|
||||
}
|
||||
|
||||
private boolean needsImmediateFetch(FetchTiming fetchTiming) {
|
||||
if ( fetchTiming == FetchTiming.IMMEDIATE ) {
|
||||
return true;
|
||||
}
|
||||
else if ( !entityMappingType.isConcreteProxy() ) {
|
||||
// Consider all associations annotated with @NotFound as EAGER
|
||||
// and LAZY one-to-one that are not instrumented and not optional.
|
||||
// When resolving the concrete entity type we can preserve laziness
|
||||
// and handle not found actions based on the discriminator value
|
||||
return hasNotFoundAction()
|
||||
|| entityMappingType.getSoftDeleteMapping() != null
|
||||
|| ( !entityMappingType.getEntityPersister().isInstrumented()
|
||||
&& cardinality == Cardinality.ONE_TO_ONE && isOptional );
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private TableGroup determineTableGroupForFetch(
|
||||
NavigablePath fetchablePath,
|
||||
FetchParent fetchParent,
|
||||
|
|
|
@ -195,6 +195,7 @@ import org.hibernate.metamodel.mapping.internal.CompoundNaturalIdMapping;
|
|||
import org.hibernate.metamodel.mapping.internal.DiscriminatedAssociationAttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.internal.DiscriminatorTypeImpl;
|
||||
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
|
||||
import org.hibernate.loader.ast.internal.EntityConcreteTypeLoader;
|
||||
import org.hibernate.metamodel.mapping.internal.EntityRowIdMappingImpl;
|
||||
import org.hibernate.metamodel.mapping.internal.EntityVersionMappingImpl;
|
||||
import org.hibernate.metamodel.mapping.internal.ExplicitColumnDiscriminatorMappingImpl;
|
||||
|
@ -452,6 +453,8 @@ public abstract class AbstractEntityPersister
|
|||
|
||||
private EntityMappingType superMappingType;
|
||||
private SortedMap<String, EntityMappingType> subclassMappingTypes;
|
||||
private final boolean concreteProxy;
|
||||
private EntityConcreteTypeLoader concreteTypeLoader;
|
||||
|
||||
private EntityIdentifierMapping identifierMapping;
|
||||
private NaturalIdMapping naturalIdMapping;
|
||||
|
@ -542,6 +545,10 @@ public abstract class AbstractEntityPersister
|
|||
assert javaType != null;
|
||||
this.implementsLifecycle = Lifecycle.class.isAssignableFrom( javaType.getJavaTypeClass() );
|
||||
|
||||
concreteProxy = isPolymorphic()
|
||||
&& ( getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() || hasProxy() )
|
||||
&& persistentClass.isConcreteProxy();
|
||||
|
||||
final Dialect dialect = creationContext.getDialect();
|
||||
|
||||
batchSize = persistentClass.getBatchSize() < 0
|
||||
|
@ -4288,6 +4295,24 @@ public abstract class AbstractEntityPersister
|
|||
return entityMetamodel.isExplicitPolymorphism();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConcreteProxy() {
|
||||
return concreteProxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityMappingType resolveConcreteProxyTypeForId(Object id, SharedSessionContractImplementor session) {
|
||||
if ( !concreteProxy ) {
|
||||
return this;
|
||||
}
|
||||
|
||||
EntityConcreteTypeLoader concreteTypeLoader = this.concreteTypeLoader;
|
||||
if ( concreteTypeLoader == null ) {
|
||||
this.concreteTypeLoader = concreteTypeLoader = new EntityConcreteTypeLoader( this, session.getFactory() );
|
||||
}
|
||||
return concreteTypeLoader.getConcreteType( id, session );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getKeyColumnNames() {
|
||||
return getIdentifierColumnNames();
|
||||
|
|
|
@ -1240,7 +1240,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
|||
|
||||
@Override
|
||||
public TableDetails getMappedTableDetails() {
|
||||
return getTableMapping( getTableMappings().length - 1 );
|
||||
// Subtract the number of secondary tables (tableSpan - coreTableSpan) and get the last table mapping
|
||||
return getTableMapping( getTableMappings().length - ( tableSpan - coreTableSpan ) - 1 );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -359,7 +359,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
|||
);
|
||||
}
|
||||
else if ( concreteDescriptor != null
|
||||
|| ( concreteDescriptor = determineConcreteEntityDescriptor( rowProcessingState ) ) != null ) {
|
||||
|| ( concreteDescriptor = determineConcreteEntityDescriptor( rowProcessingState, discriminatorAssembler, entityDescriptor ) ) != null ) {
|
||||
// 2) build the EntityKey
|
||||
entityKey = new EntityKey( id, concreteDescriptor );
|
||||
state = State.KEY_RESOLVED;
|
||||
|
@ -375,7 +375,10 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
|||
}
|
||||
}
|
||||
|
||||
private EntityPersister determineConcreteEntityDescriptor(RowProcessingState rowProcessingState)
|
||||
public static @Nullable EntityPersister determineConcreteEntityDescriptor(
|
||||
RowProcessingState rowProcessingState,
|
||||
BasicResultAssembler<?> discriminatorAssembler,
|
||||
EntityPersister entityDescriptor)
|
||||
throws WrongClassException {
|
||||
if ( discriminatorAssembler == null
|
||||
|| rowProcessingState.isQueryCacheHit() && !entityDescriptor.storeDiscriminatorInShallowQueryCacheLayout() ) {
|
||||
|
@ -389,7 +392,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
|||
final DiscriminatorValueDetails discriminatorDetails =
|
||||
discriminatorMapping.resolveDiscriminatorValue( discriminator );
|
||||
if ( discriminatorDetails == null ) {
|
||||
return entityDescriptor;
|
||||
assert discriminator == null : "Discriminator details should only be null for null values";
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
final EntityMappingType indicatedEntity = discriminatorDetails.getIndicatedEntity();
|
||||
|
@ -407,6 +411,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object initializeIdentifier(RowProcessingState rowProcessingState) {
|
||||
final JdbcValuesSourceProcessingState jdbcValuesSourceProcessingState =
|
||||
rowProcessingState.getJdbcValuesSourceProcessingState();
|
||||
|
@ -432,7 +437,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
|||
// The id can only be the entity instance if this is a non-aggregated id that has no containing class
|
||||
&& entityDescriptor.getIdentifierMapping() instanceof CompositeIdentifierMapping
|
||||
&& !( (CompositeIdentifierMapping) entityDescriptor.getIdentifierMapping() ).hasContainingClass()
|
||||
&& ( concreteDescriptor = determineConcreteEntityDescriptor( rowProcessingState ) ) != null
|
||||
&& ( concreteDescriptor = determineConcreteEntityDescriptor( rowProcessingState, discriminatorAssembler, entityDescriptor ) ) != null
|
||||
&& concreteDescriptor.isInstance( id );
|
||||
}
|
||||
|
||||
|
|
|
@ -14,17 +14,17 @@ import org.hibernate.spi.NavigablePath;
|
|||
import org.hibernate.sql.results.graph.AssemblerCreationState;
|
||||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
import org.hibernate.sql.results.graph.DomainResultAssembler;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.Fetch;
|
||||
import org.hibernate.sql.results.graph.FetchParent;
|
||||
import org.hibernate.sql.results.graph.FetchParentAccess;
|
||||
import org.hibernate.sql.results.graph.Fetchable;
|
||||
import org.hibernate.sql.results.graph.InitializerProducer;
|
||||
import org.hibernate.sql.results.graph.basic.BasicFetch;
|
||||
import org.hibernate.sql.results.graph.entity.EntityFetch;
|
||||
import org.hibernate.sql.results.graph.entity.EntityInitializer;
|
||||
import org.hibernate.sql.results.graph.internal.ImmutableFetchList;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -34,6 +34,7 @@ public abstract class AbstractNonJoinedEntityFetch implements EntityFetch,
|
|||
private final ToOneAttributeMapping fetchedModelPart;
|
||||
private final FetchParent fetchParent;
|
||||
private final DomainResult<?> keyResult;
|
||||
private final BasicFetch<?> discriminatorFetch;
|
||||
private final boolean selectByUniqueKey;
|
||||
|
||||
public AbstractNonJoinedEntityFetch(
|
||||
|
@ -41,11 +42,29 @@ public abstract class AbstractNonJoinedEntityFetch implements EntityFetch,
|
|||
ToOneAttributeMapping fetchedModelPart,
|
||||
FetchParent fetchParent,
|
||||
DomainResult<?> keyResult,
|
||||
boolean selectDiscriminator,
|
||||
boolean selectByUniqueKey,
|
||||
DomainResultCreationState creationState) {
|
||||
this.navigablePath = navigablePath;
|
||||
this.fetchedModelPart = fetchedModelPart;
|
||||
this.fetchParent = fetchParent;
|
||||
this.keyResult = keyResult;
|
||||
this.discriminatorFetch = selectDiscriminator ? creationState.visitDiscriminatorFetch( this ) : null;
|
||||
this.selectByUniqueKey = selectByUniqueKey;
|
||||
}
|
||||
|
||||
protected AbstractNonJoinedEntityFetch(
|
||||
NavigablePath navigablePath,
|
||||
ToOneAttributeMapping fetchedModelPart,
|
||||
FetchParent fetchParent,
|
||||
DomainResult<?> keyResult,
|
||||
BasicFetch<?> discriminatorFetch,
|
||||
boolean selectByUniqueKey) {
|
||||
this.navigablePath = navigablePath;
|
||||
this.fetchedModelPart = fetchedModelPart;
|
||||
this.fetchParent = fetchParent;
|
||||
this.keyResult = keyResult;
|
||||
this.discriminatorFetch = discriminatorFetch;
|
||||
this.selectByUniqueKey = selectByUniqueKey;
|
||||
}
|
||||
|
||||
|
@ -99,6 +118,9 @@ public abstract class AbstractNonJoinedEntityFetch implements EntityFetch,
|
|||
if ( keyResult != null ) {
|
||||
keyResult.collectValueIndexesToCache( valueIndexes );
|
||||
}
|
||||
if ( discriminatorFetch != null ) {
|
||||
discriminatorFetch.collectValueIndexesToCache( valueIndexes );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -110,6 +132,10 @@ public abstract class AbstractNonJoinedEntityFetch implements EntityFetch,
|
|||
return keyResult;
|
||||
}
|
||||
|
||||
public BasicFetch<?> getDiscriminatorFetch() {
|
||||
return discriminatorFetch;
|
||||
}
|
||||
|
||||
public boolean isSelectByUniqueKey() {
|
||||
return selectByUniqueKey;
|
||||
}
|
||||
|
|
|
@ -11,8 +11,10 @@ import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
|
|||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.results.graph.AssemblerCreationState;
|
||||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.FetchParent;
|
||||
import org.hibernate.sql.results.graph.FetchParentAccess;
|
||||
import org.hibernate.sql.results.graph.basic.BasicResultAssembler;
|
||||
import org.hibernate.sql.results.graph.entity.EntityInitializer;
|
||||
|
||||
/**
|
||||
|
@ -20,14 +22,22 @@ import org.hibernate.sql.results.graph.entity.EntityInitializer;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public class EntityDelayedFetchImpl extends AbstractNonJoinedEntityFetch {
|
||||
|
||||
public EntityDelayedFetchImpl(
|
||||
FetchParent fetchParent,
|
||||
ToOneAttributeMapping fetchedAttribute,
|
||||
NavigablePath navigablePath,
|
||||
DomainResult<?> keyResult,
|
||||
boolean selectByUniqueKey) {
|
||||
super( navigablePath, fetchedAttribute, fetchParent, keyResult, selectByUniqueKey );
|
||||
boolean selectByUniqueKey,
|
||||
DomainResultCreationState creationState) {
|
||||
super(
|
||||
navigablePath,
|
||||
fetchedAttribute,
|
||||
fetchParent,
|
||||
keyResult,
|
||||
fetchedAttribute.getEntityMappingType().getEntityPersister().isConcreteProxy(),
|
||||
selectByUniqueKey,
|
||||
creationState
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -42,7 +52,10 @@ public class EntityDelayedFetchImpl extends AbstractNonJoinedEntityFetch {
|
|||
getNavigablePath(),
|
||||
getEntityValuedModelPart(),
|
||||
isSelectByUniqueKey(),
|
||||
getKeyResult().createResultAssembler( parentAccess, creationState )
|
||||
getKeyResult().createResultAssembler( parentAccess, creationState ),
|
||||
getDiscriminatorFetch() != null
|
||||
? (BasicResultAssembler<?>) getDiscriminatorFetch().createResultAssembler( parentAccess, creationState )
|
||||
: null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.sql.results.graph.entity.internal;
|
|||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.FetchNotFoundException;
|
||||
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
|
||||
import org.hibernate.engine.spi.EntityHolder;
|
||||
import org.hibernate.engine.spi.EntityKey;
|
||||
|
@ -26,12 +27,15 @@ import org.hibernate.sql.exec.spi.ExecutionContext;
|
|||
import org.hibernate.sql.results.graph.DomainResultAssembler;
|
||||
import org.hibernate.sql.results.graph.FetchParentAccess;
|
||||
import org.hibernate.sql.results.graph.Initializer;
|
||||
import org.hibernate.sql.results.graph.basic.BasicResultAssembler;
|
||||
import org.hibernate.sql.results.graph.entity.EntityInitializer;
|
||||
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
|
||||
import org.hibernate.type.Type;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static org.hibernate.sql.results.graph.entity.AbstractEntityInitializer.determineConcreteEntityDescriptor;
|
||||
|
||||
/**
|
||||
* @author Andrea Boriero
|
||||
* @author Steve Ebersole
|
||||
|
@ -46,6 +50,7 @@ public class EntityDelayedFetchInitializer implements EntityInitializer {
|
|||
private final ToOneAttributeMapping referencedModelPart;
|
||||
private final boolean selectByUniqueKey;
|
||||
private final DomainResultAssembler<?> identifierAssembler;
|
||||
private final BasicResultAssembler<?> discriminatorAssembler;
|
||||
|
||||
protected boolean parentShallowCached;
|
||||
|
||||
|
@ -59,9 +64,10 @@ public class EntityDelayedFetchInitializer implements EntityInitializer {
|
|||
NavigablePath fetchedNavigable,
|
||||
ToOneAttributeMapping referencedModelPart,
|
||||
boolean selectByUniqueKey,
|
||||
DomainResultAssembler<?> identifierAssembler) {
|
||||
// associations marked with `@NotFound` are ALWAYS eagerly fetched
|
||||
assert referencedModelPart.getNotFoundAction() == null;
|
||||
DomainResultAssembler<?> identifierAssembler,
|
||||
BasicResultAssembler<?> discriminatorAssembler) {
|
||||
// associations marked with `@NotFound` are ALWAYS eagerly fetched, unless we're resolving the concrete type
|
||||
assert !referencedModelPart.hasNotFoundAction() || referencedModelPart.getEntityMappingType().isConcreteProxy();
|
||||
|
||||
this.parentAccess = parentAccess;
|
||||
this.navigablePath = fetchedNavigable;
|
||||
|
@ -71,6 +77,7 @@ public class EntityDelayedFetchInitializer implements EntityInitializer {
|
|||
this.referencedModelPart = referencedModelPart;
|
||||
this.selectByUniqueKey = selectByUniqueKey;
|
||||
this.identifierAssembler = identifierAssembler;
|
||||
this.discriminatorAssembler = discriminatorAssembler;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -109,7 +116,28 @@ public class EntityDelayedFetchInitializer implements EntityInitializer {
|
|||
}
|
||||
else {
|
||||
final SharedSessionContractImplementor session = rowProcessingState.getSession();
|
||||
final EntityPersister concreteDescriptor = referencedModelPart.getEntityMappingType().getEntityPersister();
|
||||
|
||||
final EntityPersister entityPersister = referencedModelPart.getEntityMappingType().getEntityPersister();
|
||||
final EntityPersister concreteDescriptor;
|
||||
if ( discriminatorAssembler != null ) {
|
||||
concreteDescriptor = determineConcreteEntityDescriptor(
|
||||
rowProcessingState,
|
||||
discriminatorAssembler,
|
||||
entityPersister
|
||||
);
|
||||
if ( concreteDescriptor == null ) {
|
||||
// If we find no discriminator it means there's no entity in the target table
|
||||
if ( !referencedModelPart.isOptional() ) {
|
||||
throw new FetchNotFoundException( entityPersister.getEntityName(), identifier );
|
||||
}
|
||||
entityInstance = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
concreteDescriptor = entityPersister;
|
||||
}
|
||||
|
||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||
if ( selectByUniqueKey ) {
|
||||
final String uniqueKeyPropertyName = referencedModelPart.getReferencedPropertyName();
|
||||
|
|
|
@ -29,8 +29,8 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
|
|||
NavigablePath navigablePath,
|
||||
DomainResult<?> keyResult,
|
||||
boolean selectByUniqueKey,
|
||||
@SuppressWarnings("unused") DomainResultCreationState creationState) {
|
||||
super( navigablePath, fetchedAttribute, fetchParent, keyResult, selectByUniqueKey );
|
||||
DomainResultCreationState creationState) {
|
||||
super( navigablePath, fetchedAttribute, fetchParent, keyResult, false, selectByUniqueKey, creationState );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,6 +42,7 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
|
|||
original.getFetchedMapping(),
|
||||
original.getFetchParent(),
|
||||
original.getKeyResult(),
|
||||
original.getDiscriminatorFetch(),
|
||||
original.isSelectByUniqueKey()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,9 +14,11 @@ import org.hibernate.sql.results.graph.AssemblerCreationState;
|
|||
import org.hibernate.sql.results.graph.BiDirectionalFetch;
|
||||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
import org.hibernate.sql.results.graph.DomainResultAssembler;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.FetchParent;
|
||||
import org.hibernate.sql.results.graph.FetchParentAccess;
|
||||
import org.hibernate.sql.results.graph.Initializer;
|
||||
import org.hibernate.sql.results.graph.basic.BasicResultAssembler;
|
||||
import org.hibernate.sql.results.graph.entity.EntityInitializer;
|
||||
import org.hibernate.sql.results.graph.entity.internal.AbstractNonJoinedEntityFetch;
|
||||
import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchInitializer;
|
||||
|
@ -41,8 +43,19 @@ public class CircularFetchImpl extends AbstractNonJoinedEntityFetch implements B
|
|||
FetchParent fetchParent,
|
||||
boolean selectByUniqueKey,
|
||||
NavigablePath referencedNavigablePath,
|
||||
DomainResult<?> keyResult) {
|
||||
super( navigablePath, referencedModelPart, fetchParent, keyResult, selectByUniqueKey );
|
||||
DomainResult<?> keyResult,
|
||||
DomainResultCreationState creationState) {
|
||||
super(
|
||||
navigablePath,
|
||||
referencedModelPart,
|
||||
fetchParent,
|
||||
keyResult,
|
||||
timing == FetchTiming.DELAYED && referencedModelPart.getEntityMappingType()
|
||||
.getEntityPersister()
|
||||
.isConcreteProxy(),
|
||||
selectByUniqueKey,
|
||||
creationState
|
||||
);
|
||||
this.timing = timing;
|
||||
this.referencedNavigablePath = referencedNavigablePath;
|
||||
}
|
||||
|
@ -53,6 +66,7 @@ public class CircularFetchImpl extends AbstractNonJoinedEntityFetch implements B
|
|||
original.getFetchedMapping(),
|
||||
original.getFetchParent(),
|
||||
original.getKeyResult(),
|
||||
original.getDiscriminatorFetch(),
|
||||
original.isSelectByUniqueKey()
|
||||
);
|
||||
this.timing = original.timing;
|
||||
|
@ -103,7 +117,10 @@ public class CircularFetchImpl extends AbstractNonJoinedEntityFetch implements B
|
|||
getNavigablePath(),
|
||||
getFetchedMapping(),
|
||||
isSelectByUniqueKey(),
|
||||
getKeyResult().createResultAssembler( parentAccess, creationState )
|
||||
getKeyResult().createResultAssembler( parentAccess, creationState ),
|
||||
getDiscriminatorFetch() != null
|
||||
? (BasicResultAssembler<?>) getDiscriminatorFetch().createResultAssembler( parentAccess, creationState )
|
||||
: null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -132,13 +149,15 @@ public class CircularFetchImpl extends AbstractNonJoinedEntityFetch implements B
|
|||
NavigablePath referencedPath,
|
||||
ToOneAttributeMapping fetchable,
|
||||
boolean selectByUniqueKey,
|
||||
DomainResultAssembler<?> resultAssembler) {
|
||||
DomainResultAssembler<?> resultAssembler,
|
||||
BasicResultAssembler<?> discriminatorAssembler) {
|
||||
return new EntityDelayedFetchInitializer(
|
||||
parentAccess,
|
||||
referencedPath,
|
||||
fetchable,
|
||||
selectByUniqueKey,
|
||||
resultAssembler
|
||||
resultAssembler,
|
||||
discriminatorAssembler
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,598 @@
|
|||
/*
|
||||
* 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.orm.test.proxy.concrete;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.annotations.ConcreteProxy;
|
||||
import org.hibernate.boot.MetadataSources;
|
||||
import org.hibernate.boot.SessionFactoryBuilder;
|
||||
import org.hibernate.sql.ast.SqlAstJoinType;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.DiscriminatorColumn;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
public abstract class AbstractConcreteProxyTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||
@Test
|
||||
public void testSingleTable() {
|
||||
final SQLStatementInspector inspector = getStatementInspector();
|
||||
inspector.clear();
|
||||
// test find and association
|
||||
inSession( session -> {
|
||||
final SingleParent parent1 = session.find( SingleParent.class, 1L );
|
||||
assertThat( parent1.getSingle(), instanceOf( SingleSubChild1.class ) );
|
||||
assertThat( Hibernate.isInitialized( parent1.getSingle() ), is( false ) );
|
||||
final SingleSubChild1 proxy = (SingleSubChild1) parent1.getSingle();
|
||||
assertThat( Hibernate.isInitialized( proxy ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 1 );
|
||||
inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "disc_col", 1 );
|
||||
} );
|
||||
inspector.clear();
|
||||
// test query and association
|
||||
inSession( session -> {
|
||||
final SingleParent parent2 = session.createQuery(
|
||||
"from SingleParent where id = 2",
|
||||
SingleParent.class
|
||||
).getSingleResult();
|
||||
assertThat( parent2.getSingle(), instanceOf( SingleChild2.class ) );
|
||||
assertThat( Hibernate.isInitialized( parent2.getSingle() ), is( false ) );
|
||||
final SingleChild2 proxy = (SingleChild2) parent2.getSingle();
|
||||
assertThat( Hibernate.isInitialized( proxy ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 1 );
|
||||
inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "disc_col", 1 );
|
||||
} );
|
||||
inspector.clear();
|
||||
// test get reference
|
||||
inSession( session -> {
|
||||
final SingleChild1 proxy1 = session.getReference( SingleChild1.class, 1L );
|
||||
assertThat( proxy1, instanceOf( SingleSubChild1.class ) );
|
||||
assertThat( Hibernate.isInitialized( proxy1 ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "disc_col", 2 );
|
||||
inspector.clear();
|
||||
final SingleBase proxy2 = session.byId( SingleBase.class ).getReference( 2L );
|
||||
assertThat( proxy2, instanceOf( SingleChild2.class ) );
|
||||
assertThat( Hibernate.isInitialized( proxy2 ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "disc_col", 1 );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJoined() {
|
||||
final SQLStatementInspector inspector = getStatementInspector();
|
||||
inspector.clear();
|
||||
// test find and association
|
||||
inSession( session -> {
|
||||
final JoinedParent parent1 = session.find( JoinedParent.class, 1L );
|
||||
assertThat( Hibernate.isInitialized( parent1.getJoined() ), is( false ) );
|
||||
assertThat( parent1.getJoined(), instanceOf( JoinedSubChild1.class ) );
|
||||
final JoinedSubChild1 proxy = (JoinedSubChild1) parent1.getJoined();
|
||||
assertThat( Hibernate.isInitialized( proxy ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 4 );
|
||||
} );
|
||||
inspector.clear();
|
||||
// test query and association
|
||||
inSession( session -> {
|
||||
final JoinedParent parent2 = session.createQuery(
|
||||
"from JoinedParent where id = 2",
|
||||
JoinedParent.class
|
||||
).getSingleResult();
|
||||
assertThat( Hibernate.isInitialized( parent2.getJoined() ), is( false ) );
|
||||
assertThat( parent2.getJoined(), instanceOf( JoinedChild2.class ) );
|
||||
final JoinedChild2 proxy = (JoinedChild2) parent2.getJoined();
|
||||
assertThat( Hibernate.isInitialized( proxy ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 4 );
|
||||
} );
|
||||
inspector.clear();
|
||||
// test get reference
|
||||
inSession( session -> {
|
||||
final JoinedChild1 proxy1 = session.getReference( JoinedChild1.class, 1L );
|
||||
assertThat( proxy1, instanceOf( JoinedSubChild1.class ) );
|
||||
assertThat( Hibernate.isInitialized( proxy1 ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 1 );
|
||||
inspector.clear();
|
||||
final JoinedBase proxy2 = session.byId( JoinedBase.class ).getReference( 2L );
|
||||
assertThat( proxy2, instanceOf( JoinedChild2.class ) );
|
||||
assertThat( Hibernate.isInitialized( proxy2 ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 3 );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJoinedDisc() {
|
||||
final SQLStatementInspector inspector = getStatementInspector();
|
||||
inspector.clear();
|
||||
// test find and association
|
||||
inSession( session -> {
|
||||
final JoinedDiscParent parent1 = session.find( JoinedDiscParent.class, 1L );
|
||||
assertThat( Hibernate.isInitialized( parent1.getJoinedDisc() ), is( false ) );
|
||||
assertThat( parent1.getJoinedDisc(), instanceOf( JoinedDiscSubChild1.class ) );
|
||||
final JoinedDiscSubChild1 proxy = (JoinedDiscSubChild1) parent1.getJoinedDisc();
|
||||
assertThat( Hibernate.isInitialized( proxy ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 1 );
|
||||
inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "disc_col", 1 );
|
||||
} );
|
||||
inspector.clear();
|
||||
// test query and association
|
||||
inSession( session -> {
|
||||
final JoinedDiscParent parent2 = session.createQuery(
|
||||
"from JoinedDiscParent where id = 2",
|
||||
JoinedDiscParent.class
|
||||
).getSingleResult();
|
||||
assertThat( Hibernate.isInitialized( parent2.getJoinedDisc() ), is( false ) );
|
||||
assertThat( parent2.getJoinedDisc(), instanceOf( JoinedDiscChild2.class ) );
|
||||
final JoinedDiscChild2 proxy = (JoinedDiscChild2) parent2.getJoinedDisc();
|
||||
assertThat( Hibernate.isInitialized( proxy ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 1 );
|
||||
inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "disc_col", 1 );
|
||||
} );
|
||||
inspector.clear();
|
||||
// test get reference
|
||||
inSession( session -> {
|
||||
final JoinedDiscChild1 proxy1 = session.getReference( JoinedDiscChild1.class, 1L );
|
||||
assertThat( proxy1, instanceOf( JoinedDiscSubChild1.class ) );
|
||||
assertThat( Hibernate.isInitialized( proxy1 ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 0 );
|
||||
inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "disc_col", 1 );
|
||||
inspector.clear();
|
||||
final JoinedDiscBase proxy2 = session.byId( JoinedDiscBase.class ).getReference( 2L );
|
||||
assertThat( proxy2, instanceOf( JoinedDiscChild2.class ) );
|
||||
assertThat( Hibernate.isInitialized( proxy2 ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfJoins( 0, 0 );
|
||||
inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "disc_col", 1 );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnion() {
|
||||
final SQLStatementInspector inspector = getStatementInspector();
|
||||
inspector.clear();
|
||||
// test find and association
|
||||
inSession( session -> {
|
||||
final UnionParent parent1 = session.find( UnionParent.class, 1L );
|
||||
assertThat( Hibernate.isInitialized( parent1.getUnion() ), is( false ) );
|
||||
assertThat( parent1.getUnion(), instanceOf( UnionSubChild1.class ) );
|
||||
final UnionSubChild1 proxy = (UnionSubChild1) parent1.getUnion();
|
||||
assertThat( Hibernate.isInitialized( proxy ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 1 );
|
||||
inspector.assertNumberOfOccurrenceInQuery( 0, "union", 3 );
|
||||
} );
|
||||
inspector.clear();
|
||||
// test query and association
|
||||
inSession( session -> {
|
||||
final UnionParent parent2 = session.createQuery(
|
||||
"from UnionParent where id = 2",
|
||||
UnionParent.class
|
||||
).getSingleResult();
|
||||
assertThat( Hibernate.isInitialized( parent2.getUnion() ), is( false ) );
|
||||
assertThat( parent2.getUnion(), instanceOf( UnionChild2.class ) );
|
||||
final UnionChild2 proxy = (UnionChild2) parent2.getUnion();
|
||||
assertThat( Hibernate.isInitialized( proxy ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 1 );
|
||||
inspector.assertNumberOfOccurrenceInQuery( 0, "union", 3 );
|
||||
} );
|
||||
inspector.clear();
|
||||
// test get reference
|
||||
inSession( session -> {
|
||||
final UnionChild1 proxy1 = session.getReference( UnionChild1.class, 1L );
|
||||
assertThat( proxy1, instanceOf( UnionSubChild1.class ) );
|
||||
assertThat( Hibernate.isInitialized( proxy1 ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfOccurrenceInQuery( 0, "union", 1 );
|
||||
inspector.clear();
|
||||
final UnionBase proxy2 = session.byId( UnionBase.class ).getReference( 2L );
|
||||
assertThat( proxy2, instanceOf( UnionChild2.class ) );
|
||||
assertThat( Hibernate.isInitialized( proxy2 ), is( false ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfOccurrenceInQuery( 0, "union", 3 );
|
||||
} );
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
inTransaction( session -> {
|
||||
session.persist( new SingleParent( 1L, new SingleSubChild1( 1L, "1", "1" ) ) );
|
||||
session.persist( new JoinedParent( 1L, new JoinedSubChild1( 1L, "1", "1" ) ) );
|
||||
session.persist( new JoinedDiscParent( 1L, new JoinedDiscSubChild1( 1L, "1", "1" ) ) );
|
||||
session.persist( new UnionParent( 1L, new UnionSubChild1( 1L, "1", "1" ) ) );
|
||||
session.persist( new SingleParent( 2L, new SingleChild2( 2L, 2 ) ) );
|
||||
session.persist( new JoinedParent( 2L, new JoinedChild2( 2L, 2 ) ) );
|
||||
session.persist( new JoinedDiscParent( 2L, new JoinedDiscChild2( 2L, 2 ) ) );
|
||||
session.persist( new UnionParent( 2L, new UnionChild2( 2L, 2 ) ) );
|
||||
} );
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
inTransaction( session -> {
|
||||
session.createMutationQuery( "delete from SingleParent" ).executeUpdate();
|
||||
session.createMutationQuery( "delete from SingleBase" ).executeUpdate();
|
||||
session.createMutationQuery( "delete from JoinedParent" ).executeUpdate();
|
||||
session.createMutationQuery( "delete from JoinedBase" ).executeUpdate();
|
||||
session.createMutationQuery( "delete from JoinedDiscParent" ).executeUpdate();
|
||||
session.createMutationQuery( "delete from JoinedDiscBase" ).executeUpdate();
|
||||
session.createMutationQuery( "delete from UnionParent" ).executeUpdate();
|
||||
session.createMutationQuery( "delete from UnionBase" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyMetadataSources(MetadataSources sources) {
|
||||
sources.addAnnotatedClasses(
|
||||
SingleParent.class,
|
||||
SingleBase.class,
|
||||
SingleChild1.class,
|
||||
SingleSubChild1.class,
|
||||
SingleChild2.class,
|
||||
JoinedParent.class,
|
||||
JoinedBase.class,
|
||||
JoinedChild1.class,
|
||||
JoinedSubChild1.class,
|
||||
JoinedChild2.class,
|
||||
JoinedDiscParent.class,
|
||||
JoinedDiscBase.class,
|
||||
JoinedDiscChild1.class,
|
||||
JoinedDiscSubChild1.class,
|
||||
JoinedDiscChild2.class,
|
||||
UnionParent.class,
|
||||
UnionBase.class,
|
||||
UnionChild1.class,
|
||||
UnionSubChild1.class,
|
||||
UnionChild2.class
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) {
|
||||
sfb.applyStatementInspector( new SQLStatementInspector() );
|
||||
}
|
||||
|
||||
protected SQLStatementInspector getStatementInspector() {
|
||||
return (SQLStatementInspector) sessionFactory().getSessionFactoryOptions().getStatementInspector();
|
||||
}
|
||||
|
||||
@Entity( name = "SingleParent" )
|
||||
public static class SingleParent {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@ManyToOne( fetch = FetchType.LAZY, cascade = CascadeType.PERSIST )
|
||||
private SingleBase single;
|
||||
|
||||
public SingleParent() {
|
||||
}
|
||||
|
||||
public SingleParent(Long id, SingleBase single) {
|
||||
this.id = id;
|
||||
this.single = single;
|
||||
}
|
||||
|
||||
public SingleBase getSingle() {
|
||||
return single;
|
||||
}
|
||||
}
|
||||
|
||||
// InheritanceType.SINGLE_TABLE
|
||||
|
||||
@Entity( name = "SingleBase" )
|
||||
@Inheritance( strategy = InheritanceType.SINGLE_TABLE )
|
||||
@DiscriminatorColumn( name = "disc_col" )
|
||||
@ConcreteProxy
|
||||
public static class SingleBase {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
public SingleBase() {
|
||||
}
|
||||
|
||||
public SingleBase(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "SingleChild1" )
|
||||
public static class SingleChild1 extends SingleBase {
|
||||
private String child1Prop;
|
||||
|
||||
public SingleChild1() {
|
||||
}
|
||||
|
||||
public SingleChild1(Long id, String child1Prop) {
|
||||
super( id );
|
||||
this.child1Prop = child1Prop;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "SingleSubChild1" )
|
||||
public static class SingleSubChild1 extends SingleChild1 {
|
||||
private String subChild1Prop;
|
||||
|
||||
public SingleSubChild1() {
|
||||
}
|
||||
|
||||
public SingleSubChild1(Long id, String child1Prop, String subChild1Prop) {
|
||||
super( id, child1Prop );
|
||||
this.subChild1Prop = subChild1Prop;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "SingleChild2" )
|
||||
public static class SingleChild2 extends SingleBase {
|
||||
private Integer child2Prop;
|
||||
|
||||
public SingleChild2() {
|
||||
}
|
||||
|
||||
public SingleChild2(Long id, Integer child2Prop) {
|
||||
super( id );
|
||||
this.child2Prop = child2Prop;
|
||||
}
|
||||
}
|
||||
|
||||
// InheritanceType.JOINED
|
||||
|
||||
@Entity( name = "JoinedParent" )
|
||||
public static class JoinedParent {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@ManyToOne( fetch = FetchType.LAZY, cascade = CascadeType.PERSIST )
|
||||
private JoinedBase joined;
|
||||
|
||||
public JoinedParent() {
|
||||
}
|
||||
|
||||
public JoinedParent(Long id, JoinedBase joined) {
|
||||
this.id = id;
|
||||
this.joined = joined;
|
||||
}
|
||||
|
||||
public JoinedBase getJoined() {
|
||||
return joined;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "JoinedBase" )
|
||||
@Inheritance( strategy = InheritanceType.JOINED )
|
||||
@ConcreteProxy
|
||||
public static class JoinedBase {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
public JoinedBase() {
|
||||
}
|
||||
|
||||
public JoinedBase(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "JoinedChild1" )
|
||||
public static class JoinedChild1 extends JoinedBase {
|
||||
private String child1Prop;
|
||||
|
||||
public JoinedChild1() {
|
||||
}
|
||||
|
||||
public JoinedChild1(Long id, String child1Prop) {
|
||||
super( id );
|
||||
this.child1Prop = child1Prop;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "JoinedSubChild1" )
|
||||
public static class JoinedSubChild1 extends JoinedChild1 {
|
||||
private String subChild1Prop;
|
||||
|
||||
public JoinedSubChild1() {
|
||||
}
|
||||
|
||||
public JoinedSubChild1(Long id, String child1Prop, String subChild1Prop) {
|
||||
super( id, child1Prop );
|
||||
this.subChild1Prop = subChild1Prop;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "JoinedChild2" )
|
||||
public static class JoinedChild2 extends JoinedBase {
|
||||
private Integer child2Prop;
|
||||
|
||||
public JoinedChild2() {
|
||||
}
|
||||
|
||||
public JoinedChild2(Long id, Integer child2Prop) {
|
||||
super( id );
|
||||
this.child2Prop = child2Prop;
|
||||
}
|
||||
}
|
||||
|
||||
// InheritanceType.JOINED + @DiscriminatorColumn
|
||||
|
||||
@Entity( name = "JoinedDiscParent" )
|
||||
public static class JoinedDiscParent {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@ManyToOne( fetch = FetchType.LAZY, cascade = CascadeType.PERSIST )
|
||||
private JoinedDiscBase joinedDisc;
|
||||
|
||||
public JoinedDiscParent() {
|
||||
}
|
||||
|
||||
public JoinedDiscParent(Long id, JoinedDiscBase joinedDisc) {
|
||||
this.id = id;
|
||||
this.joinedDisc = joinedDisc;
|
||||
}
|
||||
|
||||
public JoinedDiscBase getJoinedDisc() {
|
||||
return joinedDisc;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "JoinedDiscBase" )
|
||||
@Inheritance( strategy = InheritanceType.JOINED )
|
||||
@DiscriminatorColumn( name = "disc_col" )
|
||||
@ConcreteProxy
|
||||
public static class JoinedDiscBase {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
public JoinedDiscBase() {
|
||||
}
|
||||
|
||||
public JoinedDiscBase(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "JoinedDiscChild1" )
|
||||
public static class JoinedDiscChild1 extends JoinedDiscBase {
|
||||
private String child1Prop;
|
||||
|
||||
public JoinedDiscChild1() {
|
||||
}
|
||||
|
||||
public JoinedDiscChild1(Long id, String child1Prop) {
|
||||
super( id );
|
||||
this.child1Prop = child1Prop;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "JoinedDiscSubChild1" )
|
||||
public static class JoinedDiscSubChild1 extends JoinedDiscChild1 {
|
||||
private String subChild1Prop;
|
||||
|
||||
public JoinedDiscSubChild1() {
|
||||
}
|
||||
|
||||
public JoinedDiscSubChild1(Long id, String child1Prop, String subChild1Prop) {
|
||||
super( id, child1Prop );
|
||||
this.subChild1Prop = subChild1Prop;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "JoinedDiscChild2" )
|
||||
public static class JoinedDiscChild2 extends JoinedDiscBase {
|
||||
private Integer child2Prop;
|
||||
|
||||
public JoinedDiscChild2() {
|
||||
}
|
||||
|
||||
public JoinedDiscChild2(Long id, Integer child2Prop) {
|
||||
super( id );
|
||||
this.child2Prop = child2Prop;
|
||||
}
|
||||
}
|
||||
|
||||
// InheritanceType.TABLE_PER_CLASS
|
||||
|
||||
@Entity( name = "UnionParent" )
|
||||
public static class UnionParent {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@ManyToOne( fetch = FetchType.LAZY, cascade = CascadeType.PERSIST )
|
||||
private UnionBase union;
|
||||
|
||||
public UnionParent() {
|
||||
}
|
||||
|
||||
public UnionParent(Long id, UnionBase union) {
|
||||
this.id = id;
|
||||
this.union = union;
|
||||
}
|
||||
|
||||
public UnionBase getUnion() {
|
||||
return union;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "UnionBase" )
|
||||
@Inheritance( strategy = InheritanceType.TABLE_PER_CLASS )
|
||||
@ConcreteProxy
|
||||
public static class UnionBase {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
public UnionBase() {
|
||||
}
|
||||
|
||||
public UnionBase(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "UnionChild1" )
|
||||
public static class UnionChild1 extends UnionBase {
|
||||
private String child1Prop;
|
||||
|
||||
public UnionChild1() {
|
||||
}
|
||||
|
||||
public UnionChild1(Long id, String child1Prop) {
|
||||
super( id );
|
||||
this.child1Prop = child1Prop;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "UnionSubChild1" )
|
||||
public static class UnionSubChild1 extends UnionChild1 {
|
||||
private String subChild1Prop;
|
||||
|
||||
public UnionSubChild1() {
|
||||
}
|
||||
|
||||
public UnionSubChild1(Long id, String child1Prop, String subChild1Prop) {
|
||||
super( id, child1Prop );
|
||||
this.subChild1Prop = subChild1Prop;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "UnionChild2" )
|
||||
public static class UnionChild2 extends UnionBase {
|
||||
private Integer child2Prop;
|
||||
|
||||
public UnionChild2() {
|
||||
}
|
||||
|
||||
public UnionChild2(Long id, Integer child2Prop) {
|
||||
super( id );
|
||||
this.child2Prop = child2Prop;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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.orm.test.proxy.concrete;
|
||||
|
||||
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Version of {@link AbstractConcreteProxyTest} using bytecode-enhanced {@linkplain org.hibernate.proxy.HibernateProxy proxies}.
|
||||
*
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
@RunWith( BytecodeEnhancerRunner.class )
|
||||
public class BytecodeConcreteProxyTest extends AbstractConcreteProxyTest {
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* 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.orm.test.proxy.concrete;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.annotations.ConcreteProxy;
|
||||
import org.hibernate.sql.ast.SqlAstJoinType;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.Jira;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.MapsId;
|
||||
import jakarta.persistence.NamedAttributeNode;
|
||||
import jakarta.persistence.NamedEntityGraph;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hibernate.jpa.SpecHints.HINT_SPEC_FETCH_GRAPH;
|
||||
|
||||
/**
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
@Jira( "https://hibernate.atlassian.net/browse/HHH-16960" )
|
||||
@Jira( "https://hibernate.atlassian.net/browse/HHH-17818" )
|
||||
@DomainModel( annotatedClasses = {
|
||||
LazyOptionalOneToOneConcreteProxyTest.Person.class,
|
||||
LazyOptionalOneToOneConcreteProxyTest.PersonContact.class,
|
||||
LazyOptionalOneToOneConcreteProxyTest.BusinessContact.class,
|
||||
} )
|
||||
@SessionFactory( useCollectingStatementInspector = true )
|
||||
public class LazyOptionalOneToOneConcreteProxyTest {
|
||||
@Test
|
||||
public void testFindChildrenContact(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
|
||||
inspector.clear();
|
||||
scope.inTransaction( session -> {
|
||||
final Person person = session.find(
|
||||
Person.class,
|
||||
1L,
|
||||
Map.of( HINT_SPEC_FETCH_GRAPH, session.getEntityGraph( "Person.children" ) )
|
||||
);
|
||||
assertThat( person.getPersonContact() ).isNull();
|
||||
assertThat( person.getChildren() ).matches( Hibernate::isInitialized )
|
||||
.hasSize( 1 )
|
||||
.element( 0 )
|
||||
.extracting( Person::getPersonContact )
|
||||
.isInstanceOf( PersonContact.class )
|
||||
.matches( contact -> !Hibernate.isInitialized( contact ) );
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 3 );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindParentContact(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
|
||||
inspector.clear();
|
||||
scope.inTransaction( session -> {
|
||||
final Person person = session.find(
|
||||
Person.class,
|
||||
3L,
|
||||
Map.of( HINT_SPEC_FETCH_GRAPH, session.getEntityGraph( "Person.children" ) )
|
||||
);
|
||||
assertThat( person.getPersonContact() )
|
||||
.isInstanceOf( BusinessContact.class )
|
||||
.matches( contact -> !Hibernate.isInitialized( contact ) );
|
||||
assertThat( person.getChildren() ).matches( Hibernate::isInitialized )
|
||||
.hasSize( 2 )
|
||||
.extracting( Person::getPersonContact )
|
||||
.containsOnlyNulls();
|
||||
inspector.assertExecutedCount( 1 );
|
||||
inspector.assertNumberOfJoins( 0, SqlAstJoinType.LEFT, 3 );
|
||||
} );
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public void setUp(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
final Person parent1 = new Person( 1L, "parent1" );
|
||||
final Person child1 = new Person( 2L, "child1" );
|
||||
child1.setParent( parent1 );
|
||||
session.persist( parent1 );
|
||||
session.persist( child1 );
|
||||
session.persist( new PersonContact( 2L, child1 ) );
|
||||
final Person parent2 = new Person( 3L, "parent2" );
|
||||
final Person child2 = new Person( 4L, "child2" );
|
||||
final Person child3 = new Person( 5L, "child3" );
|
||||
child2.setParent( parent2 );
|
||||
child3.setParent( parent2 );
|
||||
session.persist( parent2 );
|
||||
session.persist( child2 );
|
||||
session.persist( child3 );
|
||||
session.persist( new BusinessContact( 3L, parent2, "business2" ) );
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public void tearDown(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
session.createMutationQuery( "delete from PersonContact" ).executeUpdate();
|
||||
session.createQuery( "from Person", Person.class ).getResultList().forEach( p -> {
|
||||
p.setParent( null );
|
||||
session.remove( p );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
@Entity( name = "Person" )
|
||||
@NamedEntityGraph( name = "Person.children", attributeNodes = @NamedAttributeNode( "children" ) )
|
||||
public static class Person {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@ManyToOne
|
||||
private Person parent;
|
||||
|
||||
@OneToOne( mappedBy = "person" )
|
||||
private PersonContact personContact;
|
||||
|
||||
@OneToMany( mappedBy = "parent" )
|
||||
private Set<Person> children = new HashSet<>();
|
||||
|
||||
public Person() {
|
||||
}
|
||||
|
||||
public Person(Long id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setParent(@Nullable Person parent) {
|
||||
this.parent = parent;
|
||||
if ( parent != null ) {
|
||||
parent.getChildren().add( this );
|
||||
}
|
||||
}
|
||||
|
||||
public PersonContact getPersonContact() {
|
||||
return personContact;
|
||||
}
|
||||
|
||||
public Set<Person> getChildren() {
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "PersonContact" )
|
||||
@ConcreteProxy
|
||||
public static class PersonContact {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@OneToOne( optional = false, fetch = FetchType.LAZY )
|
||||
@MapsId
|
||||
private Person person;
|
||||
|
||||
public PersonContact() {
|
||||
}
|
||||
|
||||
public PersonContact(Long id, Person person) {
|
||||
this.id = id;
|
||||
this.person = person;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "BusinessContact" )
|
||||
public static class BusinessContact extends PersonContact {
|
||||
private String business;
|
||||
|
||||
public BusinessContact() {
|
||||
}
|
||||
|
||||
public BusinessContact(Long id, Person person, String business) {
|
||||
super( id, person );
|
||||
this.business = business;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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.orm.test.proxy.concrete;
|
||||
|
||||
/**
|
||||
* Version of {@link AbstractConcreteProxyTest} using normal {@linkplain org.hibernate.proxy.HibernateProxy proxies}.
|
||||
*
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
public class ProxyConcreteProxyTest extends AbstractConcreteProxyTest {
|
||||
}
|
Loading…
Reference in New Issue