Fix issues with inverse FK creation and related natural id issues

This commit is contained in:
Christian Beikov 2021-02-15 17:59:47 +01:00
parent 0fafae4624
commit 5b5254fbd6
21 changed files with 371 additions and 171 deletions

View File

@ -7,9 +7,11 @@
package org.hibernate.action.internal;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.hibernate.HibernateException;
@ -22,10 +24,15 @@ import org.hibernate.cache.spi.access.NaturalIdDataAccess;
import org.hibernate.cache.spi.access.SoftLock;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.metamodel.spi.MetamodelImplementor;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.query.sqm.tree.SqmDmlStatement;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.cte.SqmCteStatement;
import org.hibernate.sql.ast.tree.insert.InsertStatement;
import org.hibernate.sql.exec.spi.ExecutionContext;
/**
* An {@link org.hibernate.engine.spi.ActionQueue} {@link org.hibernate.action.spi.Executable} for ensuring
@ -56,10 +63,10 @@ public class BulkOperationCleanupAction implements Executable, Serializable {
* @param session The session to which this request is tied.
* @param affectedQueryables The affected entity persisters.
*/
public BulkOperationCleanupAction(SharedSessionContractImplementor session, Queryable... affectedQueryables) {
public BulkOperationCleanupAction(SharedSessionContractImplementor session, EntityPersister... affectedQueryables) {
final SessionFactoryImplementor factory = session.getFactory();
final LinkedHashSet<String> spacesList = new LinkedHashSet<>();
for ( Queryable persister : affectedQueryables ) {
for ( EntityPersister persister : affectedQueryables ) {
Collections.addAll( spacesList, (String[]) persister.getQuerySpaces() );
if ( persister.canWriteToCache() ) {
@ -96,7 +103,7 @@ public class BulkOperationCleanupAction implements Executable, Serializable {
/**
* Constructs an action to cleanup "affected cache regions" based on a
* set of affected table spaces. This differs from {@link #BulkOperationCleanupAction(SharedSessionContractImplementor, Queryable[])}
* set of affected table spaces. This differs from {@link #BulkOperationCleanupAction(SharedSessionContractImplementor, EntityPersister[])}
* in that here we have the affected <strong>table names</strong>. From those
* we deduce the entity persisters which are affected based on the defined
* {@link EntityPersister#getQuerySpaces() table spaces}; and from there, we
@ -141,6 +148,37 @@ public class BulkOperationCleanupAction implements Executable, Serializable {
this.affectedTableSpaces = spacesList.toArray( new String[ 0 ] );
}
public static void schedule(ExecutionContext executionContext, SqmDmlStatement<?> statement) {
final List<EntityPersister> entityPersisters = new ArrayList<>( 1 );
final MetamodelImplementor metamodel = executionContext.getSession()
.getFactory()
.getMetamodel();
if ( !( statement instanceof InsertStatement ) ) {
entityPersisters.add( metamodel.entityPersister( statement.getTarget().getEntityName() ) );
}
for ( SqmCteStatement<?> cteStatement : statement.getCteStatements() ) {
final SqmStatement<?> cteDefinition = cteStatement.getCteDefinition();
if ( cteDefinition instanceof SqmDmlStatement<?> && !( cteDefinition instanceof InsertStatement ) ) {
entityPersisters.add(
metamodel.entityPersister( ( (SqmDmlStatement<?>) cteDefinition ).getTarget().getEntityName() )
);
}
}
schedule( executionContext, entityPersisters.toArray( new EntityPersister[0] ) );
}
public static void schedule(ExecutionContext executionContext, EntityPersister... affectedQueryables) {
final SharedSessionContractImplementor session = executionContext.getSession();
final BulkOperationCleanupAction action = new BulkOperationCleanupAction( session, affectedQueryables );
if ( session.isEventSource() ) {
( (EventSource) session ).getActionQueue().addAction( action );
}
else {
action.getAfterTransactionCompletionProcess().doAfterTransactionCompletion( true, session );
}
}
/**
* Check whether we should consider an entity as affected by the query. This

View File

@ -7,6 +7,7 @@
package org.hibernate.loader.ast.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.LockMode;
@ -15,17 +16,21 @@ import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.query.sqm.sql.FromClauseIndex;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.SqlAliasBaseManager;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
@ -41,6 +46,7 @@ import org.hibernate.sql.exec.spi.JdbcSelect;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.sql.results.internal.RowTransformerDatabaseSnapshotImpl;
import org.hibernate.type.IntegerType;
import org.jboss.logging.Logger;
@ -63,6 +69,9 @@ class DatabaseSnapshotExecutor {
SessionFactoryImplementor sessionFactory) {
this.entityDescriptor = entityDescriptor;
this.sessionFactory = sessionFactory;
this.jdbcParameters = new ArrayList<>(
entityDescriptor.getIdentifierMapping().getJdbcTypeCount()
);
final QuerySpec rootQuerySpec = new QuerySpec( true );
@ -71,7 +80,10 @@ class DatabaseSnapshotExecutor {
final LoaderSqlAstCreationState state = new LoaderSqlAstCreationState(
rootQuerySpec,
sqlAliasBaseManager,
new FromClauseIndex( null ),
LockOptions.READ,
(fetchParent, ast, creationState) -> Collections.emptyList(),
true,
sessionFactory
);
@ -88,14 +100,15 @@ class DatabaseSnapshotExecutor {
);
rootQuerySpec.getFromClause().addRoot( rootTableGroup );
state.getFromClauseAccess().registerTableGroup( rootPath, rootTableGroup );
jdbcParameters = new ArrayList<>(
entityDescriptor.getIdentifierMapping().getJdbcTypeCount()
);
//noinspection rawtypes
// We produce the same state array as if we were creating an entity snapshot
final List<DomainResult> domainResults = new ArrayList<>();
// We just need a literal to have a result set
domainResults.add(
new QueryLiteral<>( null, IntegerType.INSTANCE ).createDomainResult( null, state )
);
entityDescriptor.getIdentifierMapping().forEachSelection(
(columnIndex, selection) -> {
final TableReference tableReference = rootTableGroup.resolveTableReference( selection.getContainingTableExpression() );
@ -120,69 +133,54 @@ class DatabaseSnapshotExecutor {
jdbcParameter
)
);
final SqlSelection sqlSelection = state.getSqlExpressionResolver().resolveSqlSelection(
columnReference,
selection.getJdbcMapping().getJavaTypeDescriptor(),
sessionFactory.getTypeConfiguration()
);
//noinspection unchecked
domainResults.add(
new BasicResult<Object>(
sqlSelection.getValuesArrayPosition(),
null,
selection.getJdbcMapping().getJavaTypeDescriptor()
)
);
}
);
entityDescriptor.visitStateArrayContributors(
contributorMapping -> {
rootPath.append( contributorMapping.getAttributeName() );
contributorMapping.forEachSelection(
(columnIndex, selection) -> {
final TableReference tableReference = rootTableGroup.resolveTableReference(
selection.getContainingTableExpression() );
final ColumnReference columnReference = (ColumnReference) state.getSqlExpressionResolver()
.resolveSqlExpression(
createColumnReferenceKey( tableReference, selection.getSelectionExpression() ),
s -> new ColumnReference(
tableReference,
selection,
sessionFactory
)
);
final SqlSelection sqlSelection = state.getSqlExpressionResolver()
.resolveSqlSelection(
columnReference,
selection.getJdbcMapping().getJavaTypeDescriptor(),
sessionFactory.getTypeConfiguration()
);
//noinspection unchecked
domainResults.add(
new BasicResult<Object>(
sqlSelection.getValuesArrayPosition(),
null,
selection.getJdbcMapping().getJavaTypeDescriptor()
)
);
}
);
final NavigablePath navigablePath = rootPath.append( contributorMapping.getAttributeName() );
if ( contributorMapping instanceof SingularAttributeMapping ) {
if ( contributorMapping instanceof EntityAssociationMapping ) {
domainResults.add(
( (EntityAssociationMapping) contributorMapping ).createDelayedDomainResult(
navigablePath,
rootTableGroup,
null,
state
)
);
}
else {
domainResults.add(
contributorMapping.createDomainResult(
navigablePath,
rootTableGroup,
null,
state
)
);
}
}
else {
// TODO: Instead use a delayed collection result? Or will we remove this when redesigning this
//noinspection unchecked
domainResults.add(
new BasicResult<Object>(
0,
null,
contributorMapping.getJavaTypeDescriptor()
)
);
}
}
);
final SelectStatement selectStatement = new SelectStatement( rootQuerySpec, domainResults );
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, selectStatement )
this.jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, selectStatement )
.translate( null, QueryOptions.NONE );
}
@ -232,10 +230,6 @@ class DatabaseSnapshotExecutor {
true
);
if ( list.isEmpty() ) {
return null;
}
final int size = list.size();
assert size <= 1;
@ -243,7 +237,17 @@ class DatabaseSnapshotExecutor {
return null;
}
else {
return (Object[]) list.get( 0 );
final Object[] entitySnapshot = (Object[]) list.get( 0 );
// The result of this method is treated like the entity state array which doesn't include the id
// So we must exclude it from the array
if ( entitySnapshot.length == 1 ) {
return ArrayHelper.EMPTY_OBJECT_ARRAY;
}
else {
final Object[] state = new Object[entitySnapshot.length - 1];
System.arraycopy( entitySnapshot, 1, state, 0, state.length );
return state;
}
}
}

View File

@ -6,7 +6,6 @@
*/
package org.hibernate.loader.ast.internal;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -16,7 +15,6 @@ import javax.persistence.CacheStoreMode;
import org.hibernate.FlushMode;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.graph.spi.AppliedGraph;
import org.hibernate.metamodel.mapping.AssociationKey;
import org.hibernate.metamodel.mapping.ModelPart;
@ -34,7 +32,6 @@ import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.results.graph.DomainResultCreationState;
@ -82,22 +79,6 @@ public class LoaderSqlAstCreationState
);
}
public LoaderSqlAstCreationState(
QuerySpec querySpec,
SqlAliasBaseManager sqlAliasBaseManager,
LockOptions lockOptions,
SessionFactoryImplementor sf) {
this(
querySpec,
sqlAliasBaseManager,
new FromClauseIndex(),
lockOptions,
(fetchParent, ast, state) -> Collections.emptyList(),
true,
sf
);
}
@Override
public SqlAstCreationContext getCreationContext() {
return sf;
@ -164,48 +145,6 @@ public class LoaderSqlAstCreationState
return null;
}
private static class FromClauseIndex implements FromClauseAccess {
private TableGroup tableGroup;
@Override
public TableGroup findTableGroup(NavigablePath navigablePath) {
if ( tableGroup != null ) {
if ( tableGroup.getNavigablePath().equals( navigablePath ) ) {
return tableGroup;
}
if ( tableGroup.getNavigablePath()
.getIdentifierForTableGroup()
.equals( navigablePath.getIdentifierForTableGroup() ) ) {
return tableGroup;
}
throw new IllegalArgumentException(
"NavigablePath [" + navigablePath + "] did not match base TableGroup ["
+ tableGroup.getNavigablePath() + "]"
);
}
return null;
}
@Override
public void registerTableGroup(NavigablePath navigablePath, TableGroup tableGroup) {
assert tableGroup.getNavigablePath().equals( navigablePath );
if ( this.tableGroup != null ) {
if ( this.tableGroup != tableGroup ) {
throw new IllegalArgumentException(
"Base TableGroup [" + tableGroup.getNavigablePath() + "] already set - " + navigablePath
);
}
assert this.tableGroup.getNavigablePath().equals( navigablePath );
}
else {
this.tableGroup = tableGroup;
}
}
}
@Override
public Integer getTimeout() {
return null;

View File

@ -14,7 +14,7 @@ import org.hibernate.sql.results.graph.Fetchable;
*
* @author Steve Ebersole
*/
public interface AttributeMapping extends ModelPart, ValueMapping, Fetchable {
public interface AttributeMapping extends ModelPart, ValueMapping, Fetchable, PropertyBasedMapping {
String getAttributeName();
@Override

View File

@ -6,6 +6,12 @@
*/
package org.hibernate.metamodel.mapping;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
/**
* Commonality between `many-to-one`, `one-to-one` and `any`, as well as entity-valued collection elements and map-keys
*
@ -29,4 +35,15 @@ public interface EntityAssociationMapping extends ModelPart, Association {
default boolean incrementFetchDepth(){
return true;
}
/**
* Create a delayed DomainResult for a specific reference to this ModelPart.
*/
default <T> DomainResult<T> createDelayedDomainResult(
NavigablePath navigablePath,
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
throw new NotYetImplementedFor6Exception( getClass() );
}
}

View File

@ -0,0 +1,20 @@
/*
* 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.metamodel.mapping;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.sql.results.graph.Fetchable;
/**
* Describes an attribute with a property access.
*
* @author Christian Beikov
*/
public interface PropertyBasedMapping {
PropertyAccess getPropertyAccess();
}

View File

@ -121,8 +121,7 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
for ( int i = 0; i <= attributes.size() - 1; i++ ) {
final SingularAttributeMapping attributeMapping = attributes.get( i );
final Object domainValue = state[ attributeMapping.getStateArrayPosition() ];
values[ i ] = attributeMapping.disassemble( domainValue, session );
values[ i ] = state[ attributeMapping.getStateArrayPosition() ];
}
return values;

View File

@ -53,13 +53,13 @@ import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.CollectionIdentifierDescriptor;
import org.hibernate.metamodel.mapping.CollectionMappingType;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.PropertyBasedMapping;
import org.hibernate.metamodel.mapping.SelectionMapping;
import org.hibernate.metamodel.mapping.SelectionMappings;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ManagedMappingType;
@ -884,7 +884,7 @@ public class MappingModelCreationHelper {
if ( attributeMappingSubPart instanceof ToOneAttributeMapping ) {
final ToOneAttributeMapping referencedAttributeMapping = (ToOneAttributeMapping) attributeMappingSubPart;
setRefererencedAttributeForeignKeyDescriptor(
setReferencedAttributeForeignKeyDescriptor(
attributeMapping,
referencedAttributeMapping,
(EntityPersister) referencedAttributeMapping.getDeclaringType(),
@ -921,7 +921,8 @@ public class MappingModelCreationHelper {
attributeMapping.setForeignKeyDescriptor(
new SimpleForeignKeyDescriptor(
keySelectionMapping,
simpleFkTarget
simpleFkTarget,
( (PropertyBasedMapping) simpleFkTarget ).getPropertyAccess()
)
);
}
@ -959,18 +960,22 @@ public class MappingModelCreationHelper {
final EntityPersister referencedEntityDescriptor = creationProcess
.getEntityPersister( bootValueMapping.getReferencedEntityName() );
final String referencedPropertyName;
String referencedPropertyName;
if ( bootValueMapping instanceof OneToOne ) {
referencedPropertyName = ( (OneToOne) bootValueMapping ).getMappedByProperty();
OneToOne oneToOne = (OneToOne) bootValueMapping;
referencedPropertyName = oneToOne.getMappedByProperty();
if ( referencedPropertyName == null ) {
referencedPropertyName = oneToOne.getReferencedPropertyName();
}
}
else {
referencedPropertyName = bootValueMapping.getReferencedPropertyName();
referencedPropertyName = null;
}
if ( referencedPropertyName != null ) {
final ModelPart modelPart = referencedEntityDescriptor.findSubPart( referencedPropertyName );
if ( modelPart instanceof ToOneAttributeMapping ) {
setRefererencedAttributeForeignKeyDescriptor(
setReferencedAttributeForeignKeyDescriptor(
attributeMapping,
(ToOneAttributeMapping) modelPart,
referencedEntityDescriptor,
@ -983,6 +988,7 @@ public class MappingModelCreationHelper {
final EmbeddedForeignKeyDescriptor embeddedForeignKeyDescriptor = buildEmbeddableForeignKeyDescriptor(
(EmbeddableValuedModelPart) modelPart,
bootValueMapping,
true,
dialect,
creationProcess
);
@ -1027,7 +1033,8 @@ public class MappingModelCreationHelper {
final ForeignKeyDescriptor foreignKeyDescriptor = new SimpleForeignKeyDescriptor(
keySelectionMapping,
simpleFkTarget
simpleFkTarget,
( (PropertyBasedMapping) simpleFkTarget ).getPropertyAccess()
);
attributeMapping.setForeignKeyDescriptor( foreignKeyDescriptor );
}
@ -1053,6 +1060,21 @@ public class MappingModelCreationHelper {
Value bootValueMapping,
Dialect dialect,
MappingModelCreationProcess creationProcess) {
return buildEmbeddableForeignKeyDescriptor(
embeddableValuedModelPart,
bootValueMapping,
false,
dialect,
creationProcess
);
}
private static EmbeddedForeignKeyDescriptor buildEmbeddableForeignKeyDescriptor(
EmbeddableValuedModelPart embeddableValuedModelPart,
Value bootValueMapping,
boolean inverse,
Dialect dialect,
MappingModelCreationProcess creationProcess) {
final SelectionMappings keySelectionMappings;
final String keyTableExpression;
if ( bootValueMapping instanceof Collection ) {
@ -1082,17 +1104,29 @@ public class MappingModelCreationHelper {
creationProcess.getSqmFunctionRegistry()
);
}
return new EmbeddedForeignKeyDescriptor(
embeddableValuedModelPart,
keyTableExpression,
keySelectionMappings,
embeddableValuedModelPart.getContainingTableExpression(),
embeddableValuedModelPart.getEmbeddableTypeDescriptor(),
creationProcess
);
if ( inverse ) {
return new EmbeddedForeignKeyDescriptor(
embeddableValuedModelPart,
embeddableValuedModelPart.getContainingTableExpression(),
embeddableValuedModelPart.getEmbeddableTypeDescriptor(),
keyTableExpression,
keySelectionMappings,
creationProcess
);
}
else {
return new EmbeddedForeignKeyDescriptor(
embeddableValuedModelPart,
keyTableExpression,
keySelectionMappings,
embeddableValuedModelPart.getContainingTableExpression(),
embeddableValuedModelPart.getEmbeddableTypeDescriptor(),
creationProcess
);
}
}
private static void setRefererencedAttributeForeignKeyDescriptor(
private static void setReferencedAttributeForeignKeyDescriptor(
AbstractAttributeMapping attributeMapping,
ToOneAttributeMapping referencedAttributeMapping,
EntityPersister referencedEntityDescriptor,

View File

@ -28,10 +28,12 @@ import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.mapping.List;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.CollectionIdentifierDescriptor;
import org.hibernate.metamodel.mapping.CollectionMappingType;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.PropertyBasedMapping;
import org.hibernate.metamodel.mapping.SelectionMapping;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -339,7 +341,8 @@ public class PluralAttributeMappingImpl
);
return new SimpleForeignKeyDescriptor(
keySelectionMapping,
basicFkTargetPart
basicFkTargetPart,
( (PropertyBasedMapping) basicFkTargetPart ).getPropertyAccess()
);
}
else if ( fkTargetPart instanceof EmbeddableValuedModelPart ) {

View File

@ -23,6 +23,7 @@ import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.Clause;
@ -51,13 +52,16 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicValuedModelPart, FetchOptions {
private final SelectionMapping keySelectionMapping;
private final SelectionMapping targetSelectionMapping;
private final PropertyAccess propertyAccess;
private AssociationKey associationKey;
public SimpleForeignKeyDescriptor(
SelectionMapping keySelectionMapping,
SelectionMapping targetSelectionMapping) {
SelectionMapping targetSelectionMapping,
PropertyAccess propertyAccess) {
this.keySelectionMapping = keySelectionMapping;
this.targetSelectionMapping = targetSelectionMapping;
this.propertyAccess = propertyAccess;
}
@Override
@ -260,7 +264,7 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa
@Override
public Object disassemble(Object value, SharedSessionContractImplementor session) {
return value;
return value == null ? null : propertyAccess.getGetter().get( value );
}
@Override

View File

@ -7,12 +7,13 @@
package org.hibernate.metamodel.mapping.internal;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.PropertyBasedMapping;
import org.hibernate.property.access.spi.PropertyAccess;
/**
* @author Steve Ebersole
*/
public interface SingleAttributeIdentifierMapping extends EntityIdentifierMapping {
public interface SingleAttributeIdentifierMapping extends EntityIdentifierMapping, PropertyBasedMapping {
/**
* Access to the identifier attribute's PropertyAccess
*/

View File

@ -17,6 +17,7 @@ import org.hibernate.mapping.OneToOne;
import org.hibernate.mapping.ToOne;
import org.hibernate.metamodel.mapping.AssociationKey;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
@ -52,8 +53,10 @@ import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable;
import org.hibernate.sql.results.graph.entity.EntityFetch;
import org.hibernate.sql.results.graph.entity.EntityValuedFetchable;
import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchImpl;
import org.hibernate.sql.results.graph.entity.internal.EntityDelayedResultImpl;
import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl;
import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl;
import org.hibernate.sql.results.graph.entity.internal.EntityResultImpl;
import org.hibernate.sql.results.graph.entity.internal.EntityResultJoinedSubclassImpl;
import org.hibernate.sql.results.internal.domain.CircularBiDirectionalFetchImpl;
import org.hibernate.sql.results.internal.domain.CircularFetchImpl;
@ -494,6 +497,48 @@ public class ToOneAttributeMapping
);
}
@Override
public <T> DomainResult<T> createDelayedDomainResult(
NavigablePath navigablePath,
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
// We only need a join if the key is on the referring side i.e. this is an inverse to-one
// and if the FK refers to a non-PK, in which case we must load the whole entity
if ( !isKeyReferringSide || referencedPropertyName != null ) {
final TableGroupJoin tableGroupJoin = createTableGroupJoin(
navigablePath,
tableGroup,
null,
tableGroup.isInnerJoinPossible() ? SqlAstJoinType.INNER : SqlAstJoinType.LEFT,
null,
creationState.getSqlAstCreationState()
);
creationState.getSqlAstCreationState().getFromClauseAccess().registerTableGroup(
navigablePath,
tableGroupJoin.getJoinedGroup()
);
}
if ( referencedPropertyName == null ) {
return new EntityDelayedResultImpl(
navigablePath.append( EntityIdentifierMapping.ROLE_LOCAL_NAME ),
this,
tableGroup,
creationState
);
}
else {
// We don't support proxies based on a non-PK yet, so we must fetch the whole entity
return new EntityResultImpl(
navigablePath,
this,
null,
creationState
);
}
}
private TableGroup createTableGroupJoin(
NavigablePath fetchablePath,
LockMode lockMode,
@ -653,6 +698,12 @@ public class ToOneAttributeMapping
@Override
public int forEachJdbcValue(Object value, Clause clause, int offset, JdbcValuesConsumer consumer, SharedSessionContractImplementor session) {
return foreignKeyDescriptor.forEachJdbcValue( value, clause, offset, consumer, session );
return foreignKeyDescriptor.forEachJdbcValue(
foreignKeyDescriptor.disassemble( value, session ),
clause,
offset,
consumer,
session
);
}
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.query.sqm.internal;
import org.hibernate.action.internal.BulkOperationCleanupAction;
import org.hibernate.query.spi.NonSelectQueryPlan;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
@ -30,6 +31,7 @@ public class MultiTableDeleteQueryPlan implements NonSelectQueryPlan {
@Override
public int executeUpdate(ExecutionContext executionContext) {
BulkOperationCleanupAction.schedule( executionContext, sqmDelete );
return deleteStrategy.executeDelete( sqmDelete, domainParameterXref, executionContext );
}
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.query.sqm.internal;
import org.hibernate.action.internal.BulkOperationCleanupAction;
import org.hibernate.query.spi.NonSelectQueryPlan;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
@ -30,6 +31,7 @@ public class MultiTableUpdateQueryPlan implements NonSelectQueryPlan {
@Override
public int executeUpdate(ExecutionContext executionContext) {
BulkOperationCleanupAction.schedule( executionContext, sqmUpdate );
return mutationStrategy.executeUpdate( sqmUpdate, domainParameterXref, executionContext );
}
}

View File

@ -9,7 +9,7 @@ package org.hibernate.query.sqm.internal;
import java.util.List;
import java.util.Map;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.action.internal.BulkOperationCleanupAction;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
@ -28,15 +28,14 @@ import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcDelete;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
@ -90,6 +89,7 @@ public class SimpleDeleteQueryPlan implements NonSelectQueryPlan {
@Override
public int executeUpdate(ExecutionContext executionContext) {
BulkOperationCleanupAction.schedule( executionContext, sqmDelete );
final SharedSessionContractImplementor session = executionContext.getSession();
final SessionFactoryImplementor factory = session.getFactory();
final JdbcServices jdbcServices = factory.getJdbcServices();

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.query.sqm.internal;
import org.hibernate.action.internal.BulkOperationCleanupAction;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -78,6 +79,7 @@ public class SimpleInsertQueryPlan implements NonSelectQueryPlan {
@Override
public int executeUpdate(ExecutionContext executionContext) {
BulkOperationCleanupAction.schedule( executionContext, sqmInsert );
final SharedSessionContractImplementor session = executionContext.getSession();
final SessionFactoryImplementor factory = session.getFactory();
final JdbcServices jdbcServices = factory.getJdbcServices();

View File

@ -9,6 +9,7 @@ package org.hibernate.query.sqm.internal;
import java.util.List;
import java.util.Map;
import org.hibernate.action.internal.BulkOperationCleanupAction;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -51,6 +52,7 @@ public class SimpleUpdateQueryPlan implements NonSelectQueryPlan {
@Override
public int executeUpdate(ExecutionContext executionContext) {
BulkOperationCleanupAction.schedule( executionContext, sqmUpdate );
final SharedSessionContractImplementor session = executionContext.getSession();
final SessionFactoryImplementor factory = session.getFactory();
final JdbcServices jdbcServices = factory.getJdbcServices();

View File

@ -0,0 +1,89 @@
/*
* 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.graph.entity.internal;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.query.EntityIdentifierNavigablePath;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.tree.from.TableGroup;
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.FetchableContainer;
import org.hibernate.sql.results.graph.entity.AbstractEntityResultGraphNode;
import org.hibernate.sql.results.graph.entity.EntityInitializer;
import org.hibernate.sql.results.graph.entity.EntityResult;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import static org.hibernate.query.results.ResultsHelper.attributeName;
/**
* Selects just the FK and builds a proxy
*
* @author Christian Beikov
*/
public class EntityDelayedResultImpl implements DomainResult {
private final NavigablePath navigablePath;
private final EntityAssociationMapping entityValuedModelPart;
private final DomainResult identifierResult;
public EntityDelayedResultImpl(
NavigablePath navigablePath,
EntityAssociationMapping entityValuedModelPart,
TableGroup rootTableGroup,
DomainResultCreationState creationState) {
this.navigablePath = navigablePath;
this.entityValuedModelPart = entityValuedModelPart;
this.identifierResult = entityValuedModelPart.getForeignKeyDescriptor()
.createDomainResult(
navigablePath.append( EntityIdentifierMapping.ROLE_LOCAL_NAME ),
rootTableGroup,
null,
creationState
);
}
@Override
public JavaTypeDescriptor getResultJavaTypeDescriptor() {
return entityValuedModelPart.getAssociatedEntityMappingType().getMappedJavaTypeDescriptor();
}
@Override
public NavigablePath getNavigablePath() {
return navigablePath;
}
@Override
public String getResultVariable() {
return null;
}
@Override
public DomainResultAssembler createResultAssembler(AssemblerCreationState creationState) {
final EntityInitializer initializer = (EntityInitializer) creationState.resolveInitializer(
getNavigablePath(),
entityValuedModelPart,
() -> new EntityDelayedFetchInitializer(
getNavigablePath(),
(EntityValuedModelPart) entityValuedModelPart,
identifierResult.createResultAssembler( creationState )
)
);
return new EntityAssembler( getResultJavaTypeDescriptor(), initializer );
}
@Override
public String toString() {
return "EntityDelayedResultImpl {" + getNavigablePath() + "}";
}
}

View File

@ -7,11 +7,11 @@
package org.hibernate.orm.test.mapping.naturalid.mutable.cached;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.NotImplementedYet;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.Setting;
import static org.hibernate.cfg.AvailableSettings.GENERATE_STATISTICS;
import static org.hibernate.cfg.AvailableSettings.USE_SECOND_LEVEL_CACHE;
import static org.hibernate.testing.cache.CachingRegionFactory.DEFAULT_ACCESSTYPE;
@ -26,4 +26,5 @@ import static org.hibernate.testing.cache.CachingRegionFactory.DEFAULT_ACCESSTYP
@DomainModel( annotatedClasses = {A.class, Another.class, AllCached.class, B.class, SubClass.class} )
@SessionFactory
public class CachedMutableNaturalIdNonStrictReadWriteTest extends CachedMutableNaturalIdTest {
}

View File

@ -55,28 +55,25 @@ public class CachedMutableNaturalIdStrictReadWriteTest extends CachedMutableNatu
@Test
@TestForIssue( jiraKey = "HHH-7278" )
@NotImplementedYet( reason = "Caching is not yet implemented", strict = false )
public void testInsertedNaturalIdCachedAfterTransactionSuccess(SessionFactoryScope scope) {
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
statistics.clear();
scope.inTransaction(
(session) -> session.save( new AllCached( "it" ) )
(session) -> session.save( new Another( "it" ) )
);
scope.inTransaction(
(session) -> {
final Another it = session.bySimpleNaturalId( Another.class ).load( "it" );
assertNotNull( it );
assertEquals( 1, statistics.getNaturalIdCacheHitCount() );
}
);
assertEquals( 1, statistics.getNaturalIdCacheHitCount() );
}
@Test
@TestForIssue( jiraKey = "HHH-7278" )
@NotImplementedYet( reason = "Caching is not yet implemented", strict = false )
public void testInsertedNaturalIdNotCachedAfterTransactionFailure(SessionFactoryScope scope) {
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
statistics.clear();
@ -86,7 +83,7 @@ public class CachedMutableNaturalIdStrictReadWriteTest extends CachedMutableNatu
final Transaction transaction = session.getTransaction();
transaction.begin();
session.save( new AllCached( "it" ) );
session.save( new Another( "it" ) );
session.flush();
transaction.rollback();
@ -104,13 +101,12 @@ public class CachedMutableNaturalIdStrictReadWriteTest extends CachedMutableNatu
@Test
@TestForIssue( jiraKey = "HHH-7278" )
@NotImplementedYet( reason = "Caching is not yet implemented", strict = false )
public void testChangedNaturalIdCachedAfterTransactionSuccess(SessionFactoryScope scope) {
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
statistics.clear();
scope.inTransaction(
(session) -> session.save( new AllCached( "it" ) )
(session) -> session.save( new Another( "it" ) )
);
scope.inTransaction(
@ -136,13 +132,12 @@ public class CachedMutableNaturalIdStrictReadWriteTest extends CachedMutableNatu
@Test
@TestForIssue( jiraKey = "HHH-7278" )
@NotImplementedYet( reason = "Caching is not yet implemented", strict = false )
public void testChangedNaturalIdNotCachedAfterTransactionFailure(SessionFactoryScope scope) {
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
statistics.clear();
scope.inTransaction(
(session) -> session.save( new AllCached( "it" ) )
(session) -> session.save( new Another( "it" ) )
);
scope.inTransaction(
@ -167,13 +162,12 @@ public class CachedMutableNaturalIdStrictReadWriteTest extends CachedMutableNatu
assertNotNull( original );
}
);
assertEquals( 0, statistics );
assertEquals(0, statistics.getNaturalIdCacheHitCount());
}
@Test
@TestForIssue( jiraKey = "HHH-7309" )
@NotImplementedYet( reason = "Caching is not yet implemented", strict = false )
public void testInsertUpdateEntity_NaturalIdCachedAfterTransactionSuccess(SessionFactoryScope scope) {
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
statistics.clear();
@ -202,9 +196,8 @@ public class CachedMutableNaturalIdStrictReadWriteTest extends CachedMutableNatu
@Test
@TestForIssue( jiraKey = "HHH-9200" )
@NotImplementedYet( reason = "Caching is not yet implemented", strict = false )
public void testNaturalIdCacheStatisticsReset(SessionFactoryScope scope) {
final String naturalIdCacheRegion = Another.class.getName() + "##NaturalId";
final String naturalIdCacheRegion = "hibernate.test." + Another.class.getName() + "##NaturalId";
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
statistics.clear();

View File

@ -64,7 +64,6 @@ public abstract class CachedMutableNaturalIdTest {
}
@Test
@NotImplementedYet( reason = "Caching is not yet implemented", strict = false )
public void testNaturalIdChangedWhileDetached(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> session.save( new Another( "it" ) )