Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@
import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable;
import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable;
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
import static org.hibernate.metamodel.mapping.ForeignKeyDescriptor.Nature.TARGET;
import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer;

/**
Expand Down Expand Up @@ -931,12 +930,15 @@ public void resolveInstance(Object instance, EntityInitializerData data) {
);
data.entityHolder = persistenceContext.getEntityHolder( data.entityKey );
if ( data.entityHolder == null ) {
// Entity was most probably removed in the same session without setting the reference to null
resolveKey( data );
assert data.getState() == State.MISSING;
assert referencedModelPart instanceof ToOneAttributeMapping
&& ( (ToOneAttributeMapping) referencedModelPart ).getSideNature() == TARGET;
return;
// Entity was most probably removed in the same session without setting this association to null.
// Since this load request can happen through `find()` which doesn't auto-flush on association joins,
// the entity must be fully initialized, even if it is removed already
data.entityHolder = persistenceContext.claimEntityHolderIfPossible(
data.entityKey,
data.entityInstanceForNotify,
rowProcessingState.getJdbcValuesSourceProcessingState(),
this
);
}
if ( data.concreteDescriptor.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading()
&& isPersistentAttributeInterceptable( data.entityInstanceForNotify )
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.bytecode.enhancement.detached;

import java.util.ArrayList;
import java.util.List;

import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderColumn;

import static org.assertj.core.api.Assertions.assertThat;

@DomainModel(
annotatedClasses = {
RemoveDetachedInstanceTest.Parent.class,
RemoveDetachedInstanceTest.Child.class,
RemoveDetachedInstanceTest.ParentChild.class
}
)
@SessionFactory
@BytecodeEnhanced(runNotEnhancedAsWell = true)
@JiraKey("HHH-18631")
public class RemoveDetachedInstanceTest {
private static final Long PARENT_ID = 1L;
private static final Long CHILD_ID = 2L;
private static final Long PARENT_CHILD_ID = 3L;

@BeforeEach
public void setUp(SessionFactoryScope scope) {
scope.inTransaction( session -> {
Parent parent = new Parent( PARENT_ID, "parent" );
Child child = new Child( CHILD_ID, "child" );
ParentChild parentChild = new ParentChild( PARENT_CHILD_ID, parent, child );

session.persist( parent );
session.persist( child );
session.persist( parentChild );
} );
}

@AfterEach
public void tearDown(SessionFactoryScope scope) {
scope.inTransaction( session -> {
session.createMutationQuery( "delete from ParentChild" ).executeUpdate();
session.createMutationQuery( "delete from Child" ).executeUpdate();
session.createMutationQuery( "delete from Parent" ).executeUpdate();
} );
}

@Test
void testRemoveDetachedInstance(SessionFactoryScope scope) {
ParentChild parentChild = scope.fromTransaction( session -> session.get( ParentChild.class, PARENT_CHILD_ID ) );
assertThat( parentChild ).isNotNull();

scope.inTransaction( session -> {
session.remove( parentChild );
Parent parent = session.get( Parent.class, PARENT_ID );
assertThat( parent ).isNotNull();
List<ParentChild> pc = parent.getChildren();
assertThat( pc ).isNotNull();
assertThat( pc.size() ).isEqualTo( 1 );
assertThat( pc.get( 0 ) ).isSameAs( parentChild );
Child child = session.get( Child.class, CHILD_ID );
assertThat( child ).isNotNull();
} );

scope.inTransaction( session -> {
ParentChild pc = session.get( ParentChild.class, PARENT_CHILD_ID );
assertThat( pc ).isNull();
Parent parent = session.get( Parent.class, PARENT_ID );
assertThat( parent ).isNotNull();
assertThat( parent.getChildren() ).isEmpty();
Child child = session.get( Child.class, CHILD_ID );
assertThat( child ).isNotNull();
assertThat( child.getChildren() ).isEmpty();

} );
}

@Test
void testRemoveDetachedInstance2(SessionFactoryScope scope) {
ParentChild parentChild = scope.fromTransaction( session -> session.get( ParentChild.class, PARENT_CHILD_ID ) );
assertThat( parentChild ).isNotNull();

scope.inTransaction( session -> {
session.remove( parentChild );
session.remove( parentChild.getChild() );
Parent parent = session.get( Parent.class, PARENT_ID );
assertThat( parent ).isNotNull();
List<ParentChild> pc = parent.getChildren();
assertThat( pc ).isNotNull();
assertThat( pc.size() ).isEqualTo( 1 );
assertThat( pc.get( 0 ) ).isSameAs( parentChild );
Child child = session.get( Child.class, CHILD_ID );
assertThat( child ).isNull();
} );

scope.inTransaction( session -> {
ParentChild pc = session.get( ParentChild.class, PARENT_CHILD_ID );
assertThat( pc ).isNull();
Parent parent = session.get( Parent.class, PARENT_ID );
assertThat( parent ).isNotNull();
assertThat( parent.getChildren() ).isEmpty();
Child child = session.get( Child.class, CHILD_ID );
assertThat( child ).isNull();
} );
}

@Entity(name = "Parent")
public static class Parent {
@Id
private Long id;

private String name;

@OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
List<ParentChild> children = new ArrayList<>();

public Parent() {
}

public Parent(Long id, String name) {
this.id = id;
this.name = name;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public List<ParentChild> getChildren() {
return children;
}

public void setChildren(List<ParentChild> children) {
this.children = children;
}
}

@Entity(name = "ParentChild")
public static class ParentChild {
@Id
private Long id;

@ManyToOne
private Parent parent;

@ManyToOne
private Child child;

public ParentChild() {
}

public ParentChild(Long id, Parent parent, Child child) {
this.id = id;
this.parent = parent;
this.child = child;
parent.getChildren().add( this );
child.getChildren().add( this );
}

public Long getId() {
return id;
}

public Parent getParent() {
return parent;
}

public Child getChild() {
return child;
}

}

@Entity(name = "Child")
public static class Child {

@Id
private Long id;

private String name;

@OneToMany(mappedBy = "child")
@OrderColumn
List<ParentChild> children = new ArrayList<>();

public Child() {
}

public Child(Long id, String name) {
this.id = id;
this.name = name;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public List<ParentChild> getChildren() {
return children;
}

public void setChildren(List<ParentChild> children) {
this.children = children;
}
}
}
Loading