HHH-16833 Assertion Error when inserting two entities linked with a OneToOne relation
This commit is contained in:
parent
5d85db9211
commit
188529da36
|
@ -133,14 +133,15 @@ class DatabaseSnapshotExecutor {
|
|||
entityDescriptor.forEachAttributeMapping(
|
||||
attributeMapping -> {
|
||||
final NavigablePath navigablePath = rootPath.append( attributeMapping.getAttributeName() );
|
||||
domainResults.add(
|
||||
attributeMapping.createSnapshotDomainResult(
|
||||
final DomainResult<Object> snapshotDomainResult = attributeMapping.createSnapshotDomainResult(
|
||||
navigablePath,
|
||||
rootTableGroup,
|
||||
null,
|
||||
state
|
||||
)
|
||||
);
|
||||
if ( snapshotDomainResult != null ) {
|
||||
domainResults.add( snapshotDomainResult );
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -88,12 +88,9 @@ 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.graph.entity.internal.NotFoundSnapshotResult;
|
||||
import org.hibernate.sql.results.internal.domain.CircularBiDirectionalFetchImpl;
|
||||
import org.hibernate.sql.results.internal.domain.CircularFetchImpl;
|
||||
import org.hibernate.type.ComponentType;
|
||||
|
@ -1709,73 +1706,17 @@ public class ToOneAttributeMapping
|
|||
TableGroup parentTableGroup,
|
||||
String resultVariable,
|
||||
DomainResultCreationState creationState) {
|
||||
// We need a join if either
|
||||
// - the association is mapped with `@NotFound`
|
||||
// - the key is on the referring side i.e. this is an inverse to-one
|
||||
// and if the FK refers to a non-PK
|
||||
final boolean forceJoin = hasNotFoundAction()
|
||||
|| sideNature == ForeignKeyDescriptor.Nature.TARGET
|
||||
|| referencedPropertyName != null;
|
||||
final TableGroup tableGroupToUse;
|
||||
if ( forceJoin ) {
|
||||
tableGroupToUse = creationState.getSqlAstCreationState().getFromClauseAccess().resolveTableGroup(
|
||||
navigablePath,
|
||||
np -> {
|
||||
final TableGroupJoin tableGroupJoin = createTableGroupJoin(
|
||||
navigablePath,
|
||||
parentTableGroup,
|
||||
null,
|
||||
null,
|
||||
getDefaultSqlAstJoinType( parentTableGroup ),
|
||||
true,
|
||||
false,
|
||||
creationState.getSqlAstCreationState()
|
||||
);
|
||||
parentTableGroup.addTableGroupJoin( tableGroupJoin );
|
||||
return tableGroupJoin.getJoinedGroup();
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
tableGroupToUse = createTableGroupForDelayedFetch(
|
||||
// it's a Snapshot then we just need the value of the FK when it belongs to the parentTableGroup
|
||||
if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) {
|
||||
return foreignKeyDescriptor.getKeyPart().createDomainResult(
|
||||
navigablePath,
|
||||
parentTableGroup,
|
||||
resultVariable,
|
||||
creationState
|
||||
);
|
||||
}
|
||||
|
||||
if ( hasNotFoundAction() ) {
|
||||
assert tableGroupToUse != parentTableGroup;
|
||||
//noinspection unchecked
|
||||
return new NotFoundSnapshotResult(
|
||||
navigablePath,
|
||||
this,
|
||||
parentTableGroup,
|
||||
tableGroupToUse,
|
||||
creationState
|
||||
);
|
||||
}
|
||||
if ( referencedPropertyName == null ) {
|
||||
//noinspection unchecked
|
||||
return new EntityDelayedResultImpl(
|
||||
navigablePath.append( EntityIdentifierMapping.ID_ROLE_NAME ),
|
||||
this,
|
||||
tableGroupToUse,
|
||||
creationState
|
||||
);
|
||||
}
|
||||
else {
|
||||
// We don't support proxies based on a non-PK yet, so we must fetch the whole entity
|
||||
final EntityResultImpl entityResult = new EntityResultImpl(
|
||||
navigablePath,
|
||||
this,
|
||||
tableGroupToUse,
|
||||
null
|
||||
);
|
||||
entityResult.afterInitialize( entityResult, creationState );
|
||||
//noinspection unchecked
|
||||
return entityResult;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2360,7 +2301,20 @@ public class ToOneAttributeMapping
|
|||
|
||||
@Override
|
||||
public void addToCacheKey(MutableCacheKeyBuilder cacheKey, Object value, SharedSessionContractImplementor session) {
|
||||
foreignKeyDescriptor.addToCacheKey( cacheKey, foreignKeyDescriptor.getAssociationKeyFromSide( value, sideNature.inverse(), session ), session );
|
||||
final Object cacheValue;
|
||||
// the value may come from a database snapshot, in this case it corresponds to the value of the key and can be
|
||||
// added to the cache key
|
||||
if ( value != null && foreignKeyDescriptor.getJavaType().getJavaTypeClass() == value.getClass() ) {
|
||||
cacheValue = value;
|
||||
}
|
||||
else {
|
||||
cacheValue = foreignKeyDescriptor.getAssociationKeyFromSide(
|
||||
value,
|
||||
sideNature.inverse(),
|
||||
session
|
||||
);
|
||||
}
|
||||
foreignKeyDescriptor.addToCacheKey( cacheKey, cacheValue, session );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
* 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.FetchNotFoundException;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.results.graph.DomainResultAssembler;
|
||||
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions;
|
||||
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
/**
|
||||
* Specialized DomainResultAssembler for {@link org.hibernate.annotations.NotFound} associations
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class NotFoundSnapshotAssembler implements DomainResultAssembler {
|
||||
private final NavigablePath navigablePath;
|
||||
private final ToOneAttributeMapping toOneMapping;
|
||||
private final DomainResultAssembler<?> keyValueAssembler;
|
||||
private final DomainResultAssembler<?> targetValueAssembler;
|
||||
|
||||
public NotFoundSnapshotAssembler(
|
||||
NavigablePath navigablePath,
|
||||
ToOneAttributeMapping toOneMapping,
|
||||
DomainResultAssembler<?> keyValueAssembler,
|
||||
DomainResultAssembler<?> targetValueAssembler) {
|
||||
assert toOneMapping.hasNotFoundAction();
|
||||
|
||||
this.navigablePath = navigablePath;
|
||||
this.toOneMapping = toOneMapping;
|
||||
this.keyValueAssembler = keyValueAssembler;
|
||||
this.targetValueAssembler = targetValueAssembler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object assemble(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) {
|
||||
final Object keyValue = keyValueAssembler.assemble( rowProcessingState );
|
||||
final Object targetValue = targetValueAssembler.assemble( rowProcessingState );
|
||||
|
||||
// because of `@NotFound` these could be mismatched
|
||||
if ( keyValue != null ) {
|
||||
if ( targetValue != null ) {
|
||||
if ( toOneMapping.getNotFoundAction() == NotFoundAction.IGNORE ) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
throw new FetchNotFoundException(
|
||||
toOneMapping.getAssociatedEntityMappingType().getEntityName(),
|
||||
keyValue
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targetValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType<?> getAssembledJavaType() {
|
||||
return toOneMapping.getJavaType();
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* 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.ForeignKeyDescriptor;
|
||||
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
|
||||
import org.hibernate.spi.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.FetchParentAccess;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class NotFoundSnapshotResult implements DomainResult {
|
||||
private final NavigablePath navigablePath;
|
||||
private final ToOneAttributeMapping toOneMapping;
|
||||
|
||||
private final DomainResult<?> keyResult;
|
||||
private final DomainResult<?> targetResult;
|
||||
|
||||
public NotFoundSnapshotResult(
|
||||
NavigablePath navigablePath,
|
||||
ToOneAttributeMapping toOneMapping,
|
||||
TableGroup keyTableGroup,
|
||||
TableGroup targetTableGroup,
|
||||
DomainResultCreationState creationState) {
|
||||
this.navigablePath = navigablePath;
|
||||
this.toOneMapping = toOneMapping;
|
||||
|
||||
// NOTE: this currently assumes that only the key side can be
|
||||
// defined with `@NotFound`. That feels like a reasonable
|
||||
// assumption, though there is sme question whether to support
|
||||
// this for the inverse side also when a join table is used.
|
||||
//
|
||||
// however, that would mean a 1-1 with a join-table which
|
||||
// is pretty odd mapping
|
||||
final ForeignKeyDescriptor fkDescriptor = toOneMapping.getForeignKeyDescriptor();
|
||||
this.keyResult = fkDescriptor.createKeyDomainResult(
|
||||
navigablePath,
|
||||
targetTableGroup,
|
||||
null,
|
||||
creationState
|
||||
);
|
||||
this.targetResult = fkDescriptor.createTargetDomainResult(
|
||||
navigablePath,
|
||||
targetTableGroup,
|
||||
null,
|
||||
creationState
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigablePath getNavigablePath() {
|
||||
return navigablePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType<?> getResultJavaType() {
|
||||
return toOneMapping.getJavaType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResultVariable() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainResultAssembler<Object> createResultAssembler(
|
||||
FetchParentAccess parentAccess,
|
||||
AssemblerCreationState creationState) {
|
||||
return new NotFoundSnapshotAssembler(
|
||||
navigablePath,
|
||||
toOneMapping,
|
||||
keyResult.createResultAssembler( parentAccess, creationState ),
|
||||
targetResult.createResultAssembler( parentAccess, creationState )
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -149,11 +149,10 @@ public class ManyToOneType extends EntityType {
|
|||
return true;
|
||||
}
|
||||
|
||||
assert current.getClass().isAssignableFrom( old.getClass() );
|
||||
|
||||
// the ids are fully resolved, so compare them with isDirty(), not isModified()
|
||||
return getIdentifierOrUniqueKeyType( session.getFactory() )
|
||||
.isDirty( getIdentifier( old, session ), getIdentifier( current, session ), session );
|
||||
.isDirty( old, getIdentifier( current, session ), session );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -29,7 +29,7 @@ public class VersionedBidirectionalOneToOneMergeTest {
|
|||
AnotherTestEntity anotherTestEntity = new AnotherTestEntity();
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
session.persist( anotherTestEntity );
|
||||
session.merge( anotherTestEntity );
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -66,6 +66,8 @@ public class VersionedBidirectionalOneToOneMergeTest {
|
|||
@Id
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
String name;
|
||||
|
||||
@OneToOne(mappedBy = "anotherTestEntity")
|
||||
TestEntity testEntity;
|
||||
|
||||
|
|
Loading…
Reference in New Issue