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(
|
entityDescriptor.forEachAttributeMapping(
|
||||||
attributeMapping -> {
|
attributeMapping -> {
|
||||||
final NavigablePath navigablePath = rootPath.append( attributeMapping.getAttributeName() );
|
final NavigablePath navigablePath = rootPath.append( attributeMapping.getAttributeName() );
|
||||||
domainResults.add(
|
final DomainResult<Object> snapshotDomainResult = attributeMapping.createSnapshotDomainResult(
|
||||||
attributeMapping.createSnapshotDomainResult(
|
navigablePath,
|
||||||
navigablePath,
|
rootTableGroup,
|
||||||
rootTableGroup,
|
null,
|
||||||
null,
|
state
|
||||||
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.EntityFetch;
|
||||||
import org.hibernate.sql.results.graph.entity.EntityValuedFetchable;
|
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.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.EntityFetchJoinedImpl;
|
||||||
import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl;
|
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.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.CircularBiDirectionalFetchImpl;
|
||||||
import org.hibernate.sql.results.internal.domain.CircularFetchImpl;
|
import org.hibernate.sql.results.internal.domain.CircularFetchImpl;
|
||||||
import org.hibernate.type.ComponentType;
|
import org.hibernate.type.ComponentType;
|
||||||
|
@ -1709,73 +1706,17 @@ public class ToOneAttributeMapping
|
||||||
TableGroup parentTableGroup,
|
TableGroup parentTableGroup,
|
||||||
String resultVariable,
|
String resultVariable,
|
||||||
DomainResultCreationState creationState) {
|
DomainResultCreationState creationState) {
|
||||||
// We need a join if either
|
// it's a Snapshot then we just need the value of the FK when it belongs to the parentTableGroup
|
||||||
// - the association is mapped with `@NotFound`
|
if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) {
|
||||||
// - the key is on the referring side i.e. this is an inverse to-one
|
return foreignKeyDescriptor.getKeyPart().createDomainResult(
|
||||||
// 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(
|
|
||||||
navigablePath,
|
navigablePath,
|
||||||
parentTableGroup,
|
parentTableGroup,
|
||||||
resultVariable,
|
resultVariable,
|
||||||
creationState
|
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 {
|
else {
|
||||||
// We don't support proxies based on a non-PK yet, so we must fetch the whole entity
|
return null;
|
||||||
final EntityResultImpl entityResult = new EntityResultImpl(
|
|
||||||
navigablePath,
|
|
||||||
this,
|
|
||||||
tableGroupToUse,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
entityResult.afterInitialize( entityResult, creationState );
|
|
||||||
//noinspection unchecked
|
|
||||||
return entityResult;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2360,7 +2301,20 @@ public class ToOneAttributeMapping
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addToCacheKey(MutableCacheKeyBuilder cacheKey, Object value, SharedSessionContractImplementor session) {
|
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
|
@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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert current.getClass().isAssignableFrom( old.getClass() );
|
|
||||||
|
|
||||||
// the ids are fully resolved, so compare them with isDirty(), not isModified()
|
// the ids are fully resolved, so compare them with isDirty(), not isModified()
|
||||||
return getIdentifierOrUniqueKeyType( session.getFactory() )
|
return getIdentifierOrUniqueKeyType( session.getFactory() )
|
||||||
.isDirty( getIdentifier( old, session ), getIdentifier( current, session ), session );
|
.isDirty( old, getIdentifier( current, session ), session );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class VersionedBidirectionalOneToOneMergeTest {
|
||||||
AnotherTestEntity anotherTestEntity = new AnotherTestEntity();
|
AnotherTestEntity anotherTestEntity = new AnotherTestEntity();
|
||||||
scope.inTransaction(
|
scope.inTransaction(
|
||||||
session -> {
|
session -> {
|
||||||
session.persist( anotherTestEntity );
|
session.merge( anotherTestEntity );
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -66,6 +66,8 @@ public class VersionedBidirectionalOneToOneMergeTest {
|
||||||
@Id
|
@Id
|
||||||
UUID uuid = UUID.randomUUID();
|
UUID uuid = UUID.randomUUID();
|
||||||
|
|
||||||
|
String name;
|
||||||
|
|
||||||
@OneToOne(mappedBy = "anotherTestEntity")
|
@OneToOne(mappedBy = "anotherTestEntity")
|
||||||
TestEntity testEntity;
|
TestEntity testEntity;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue