Skip to content

Commit 534eab7

Browse files
committed
HHH-19910 Add test for issue
1 parent e57179e commit 534eab7

8 files changed

+1585
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.bytecode.enhancement.detached.collection;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
10+
import org.hibernate.Hibernate;
11+
import org.hibernate.collection.spi.PersistentCollection;
12+
import org.hibernate.engine.spi.CollectionKey;
13+
import org.hibernate.engine.spi.SessionImplementor;
14+
15+
import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
16+
import org.hibernate.testing.orm.junit.DomainModel;
17+
import org.hibernate.testing.orm.junit.Jira;
18+
import org.hibernate.testing.orm.junit.SessionFactory;
19+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
20+
import org.junit.jupiter.api.AfterEach;
21+
import org.junit.jupiter.api.BeforeEach;
22+
import org.junit.jupiter.api.Test;
23+
24+
import jakarta.persistence.Entity;
25+
import jakarta.persistence.FetchType;
26+
import jakarta.persistence.Id;
27+
import jakarta.persistence.ManyToMany;
28+
import jakarta.persistence.OrderColumn;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
32+
@DomainModel(annotatedClasses = {
33+
DetachedCollectionInitializationJoinFetchTest.EntityA.class,
34+
DetachedCollectionInitializationJoinFetchTest.EntityB.class,
35+
})
36+
@SessionFactory
37+
@BytecodeEnhanced(runNotEnhancedAsWell = true)
38+
@Jira("https://hibernate.atlassian.net/browse/HHH-19910")
39+
public class DetachedCollectionInitializationJoinFetchTest {
40+
@Test
41+
public void testTransientInstance(SessionFactoryScope scope) {
42+
scope.inTransaction( session -> {
43+
final var entityA = session.find( EntityA.class, 1L );
44+
Hibernate.initialize( entityA.getB() ); // initialize the collection
45+
session.clear();
46+
47+
fetchQuery( new ArrayList<>( entityA.getB() ), session );
48+
} );
49+
}
50+
51+
@Test
52+
public void testUninitializedDetachedInstance(SessionFactoryScope scope) {
53+
scope.inTransaction( session -> {
54+
final var entityA = session.find( EntityA.class, 1L );
55+
session.clear();
56+
57+
fetchQuery( entityA.b, session );
58+
} );
59+
}
60+
61+
@Test
62+
public void testInitializedDetachedInstance(SessionFactoryScope scope) {
63+
scope.inTransaction( session -> {
64+
final var entityA = session.find( EntityA.class, 1L );
65+
Hibernate.initialize( entityA.getB() ); // initialize the collection
66+
session.clear();
67+
68+
fetchQuery( entityA.getB(), session );
69+
} );
70+
}
71+
72+
@BeforeEach
73+
public void setUp(SessionFactoryScope scope) {
74+
scope.inTransaction( session -> {
75+
final var entityB1 = new EntityB();
76+
entityB1.id = 1L;
77+
entityB1.name = "b_1";
78+
session.persist( entityB1 );
79+
final var entityB2 = new EntityB();
80+
entityB2.id = 2L;
81+
entityB2.name = "b_2";
82+
session.persist( entityB2 );
83+
final var entityA = new EntityA();
84+
entityA.id = 1L;
85+
entityA.b.add( entityB1 );
86+
entityA.b.add( entityB2 );
87+
session.persist( entityA );
88+
} );
89+
}
90+
91+
@AfterEach
92+
public void tearDown(SessionFactoryScope scope) {
93+
scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
94+
}
95+
96+
private void fetchQuery(List<EntityB> b, SessionImplementor session) {
97+
final var entityA = new EntityA();
98+
entityA.id = 2L;
99+
entityA.setB( b );
100+
session.persist( entityA );
101+
102+
// If persist triggers lazy initialization, the EntityB instances will be persistent
103+
final boolean wasB1Managed = session.contains( b.get( 0 ) );
104+
final boolean wasB2Managed = session.contains( b.get( 1 ) );
105+
106+
final var result = session.createQuery(
107+
"from EntityA a left join fetch a.b where a.id = 2",
108+
EntityA.class
109+
).getSingleResult();
110+
111+
// We always need to initialize the collection on flush
112+
assertThat( Hibernate.isInitialized( b ) ).isTrue();
113+
114+
final var descriptor = session.getSessionFactory()
115+
.getMappingMetamodel()
116+
.getCollectionDescriptor( EntityA.class.getName() + ".b" );
117+
final PersistentCollection<?> collection = session.getPersistenceContextInternal()
118+
.getCollection( new CollectionKey( descriptor, entityA.id ) );
119+
assertThat( Hibernate.isInitialized( collection ) ).isTrue();
120+
// Currently, the collection instance is re-used if we find a detached PersistentCollection.
121+
// Not making any assertion here, as also always wrapping the value in a new instance would be acceptable
122+
// assertThat( collection ).isNotSameAs( b );
123+
124+
// The detached instances in the collection should not be the same as the
125+
// managed instances initialized in the persistence context.
126+
assertThat( result.getB().get( 0 ) == session.getReference( EntityB.class, 1L ) )
127+
.isEqualTo( wasB1Managed );
128+
assertThat( result.getB().get( 1 ) == session.getReference( EntityB.class, 2L ) )
129+
.isEqualTo( wasB2Managed );
130+
}
131+
132+
@Entity(name = "EntityA")
133+
static class EntityA {
134+
@Id
135+
private Long id;
136+
137+
private String name;
138+
139+
@ManyToMany(fetch = FetchType.LAZY)
140+
@OrderColumn
141+
private List<EntityB> b = new ArrayList<>();
142+
143+
public List<EntityB> getB() {
144+
return b;
145+
}
146+
147+
public void setB(List<EntityB> b) {
148+
this.b = b;
149+
}
150+
}
151+
152+
@Entity(name = "EntityB")
153+
static class EntityB {
154+
@Id
155+
private Long id;
156+
157+
private String name;
158+
}
159+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.bytecode.enhancement.detached.collection;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
10+
import org.hibernate.Hibernate;
11+
import org.hibernate.annotations.Fetch;
12+
import org.hibernate.annotations.FetchMode;
13+
import org.hibernate.collection.spi.PersistentCollection;
14+
import org.hibernate.engine.spi.CollectionKey;
15+
import org.hibernate.engine.spi.SessionImplementor;
16+
17+
import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
18+
import org.hibernate.testing.orm.junit.DomainModel;
19+
import org.hibernate.testing.orm.junit.Jira;
20+
import org.hibernate.testing.orm.junit.SessionFactory;
21+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
22+
import org.junit.jupiter.api.AfterEach;
23+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.Test;
25+
26+
import jakarta.persistence.Entity;
27+
import jakarta.persistence.FetchType;
28+
import jakarta.persistence.Id;
29+
import jakarta.persistence.ManyToMany;
30+
import jakarta.persistence.OrderColumn;
31+
32+
import static org.assertj.core.api.Assertions.assertThat;
33+
34+
@DomainModel(annotatedClasses = {
35+
DetachedNonJoinedCollectionInitializationJoinFetchTest.EntityA.class,
36+
DetachedNonJoinedCollectionInitializationJoinFetchTest.EntityB.class,
37+
})
38+
@SessionFactory
39+
@BytecodeEnhanced(runNotEnhancedAsWell = true)
40+
@Jira("https://hibernate.atlassian.net/browse/HHH-19910")
41+
public class DetachedNonJoinedCollectionInitializationJoinFetchTest {
42+
@Test
43+
public void testTransientInstance(SessionFactoryScope scope) {
44+
scope.inTransaction( session -> {
45+
final var entityA = session.find( EntityA.class, 1L );
46+
Hibernate.initialize( entityA.getB() ); // initialize the collection
47+
session.clear();
48+
49+
fetchQuery( new ArrayList<>( entityA.getB() ), session );
50+
} );
51+
}
52+
53+
@Test
54+
public void testUninitializedDetachedInstance(SessionFactoryScope scope) {
55+
scope.inTransaction( session -> {
56+
final var entityA = session.find( EntityA.class, 1L );
57+
session.clear();
58+
59+
fetchQuery( entityA.b, session );
60+
} );
61+
}
62+
63+
@Test
64+
public void testInitializedDetachedInstance(SessionFactoryScope scope) {
65+
scope.inTransaction( session -> {
66+
final var entityA = session.find( EntityA.class, 1L );
67+
Hibernate.initialize( entityA.getB() ); // initialize the collection
68+
session.clear();
69+
70+
fetchQuery( entityA.getB(), session );
71+
} );
72+
}
73+
74+
@BeforeEach
75+
public void setUp(SessionFactoryScope scope) {
76+
scope.inTransaction( session -> {
77+
final var entityB1 = new EntityB();
78+
entityB1.id = 1L;
79+
entityB1.name = "b_1";
80+
session.persist( entityB1 );
81+
final var entityB2 = new EntityB();
82+
entityB2.id = 2L;
83+
entityB2.name = "b_2";
84+
session.persist( entityB2 );
85+
final var entityA = new EntityA();
86+
entityA.id = 1L;
87+
entityA.b.add( entityB1 );
88+
entityA.b.add( entityB2 );
89+
session.persist( entityA );
90+
} );
91+
}
92+
93+
@AfterEach
94+
public void tearDown(SessionFactoryScope scope) {
95+
scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
96+
}
97+
98+
private void fetchQuery(List<EntityB> b, SessionImplementor session) {
99+
final var entityA = new EntityA();
100+
entityA.id = 2L;
101+
entityA.setB( b );
102+
session.persist( entityA );
103+
104+
// If persist triggers lazy initialization, the EntityB instances will be persistent
105+
final boolean wasB1Managed = session.contains( b.get( 0 ) );
106+
final boolean wasB2Managed = session.contains( b.get( 1 ) );
107+
108+
final var result = session.createQuery(
109+
"from EntityA a where a.id = 2",
110+
EntityA.class
111+
).getSingleResult();
112+
113+
// We always need to initialize the collection on flush
114+
assertThat( Hibernate.isInitialized( b ) ).isTrue();
115+
116+
final var descriptor = session.getSessionFactory()
117+
.getMappingMetamodel()
118+
.getCollectionDescriptor( EntityA.class.getName() + ".b" );
119+
final PersistentCollection<?> collection = session.getPersistenceContextInternal()
120+
.getCollection( new CollectionKey( descriptor, entityA.id ) );
121+
assertThat( Hibernate.isInitialized( collection ) ).isTrue();
122+
// Currently, the collection instance is re-used if we find a detached PersistentCollection.
123+
// Not making any assertion here, as also always wrapping the value in a new instance would be acceptable
124+
// assertThat( collection ).isNotSameAs( b );
125+
126+
// The detached instances in the collection should not be the same as the
127+
// managed instances initialized in the persistence context.
128+
assertThat( result.getB().get( 0 ) == session.getReference( EntityB.class, 1L ) )
129+
.isEqualTo( wasB1Managed );
130+
assertThat( result.getB().get( 1 ) == session.getReference( EntityB.class, 2L ) )
131+
.isEqualTo( wasB2Managed );
132+
}
133+
134+
@Entity(name = "EntityA")
135+
static class EntityA {
136+
@Id
137+
private Long id;
138+
139+
private String name;
140+
141+
@ManyToMany(fetch = FetchType.EAGER)
142+
@OrderColumn
143+
@Fetch( FetchMode.SELECT )
144+
private List<EntityB> b = new ArrayList<>();
145+
146+
public List<EntityB> getB() {
147+
return b;
148+
}
149+
150+
public void setB(List<EntityB> b) {
151+
this.b = b;
152+
}
153+
}
154+
155+
@Entity(name = "EntityB")
156+
static class EntityB {
157+
@Id
158+
private Long id;
159+
160+
private String name;
161+
}
162+
}

0 commit comments

Comments
 (0)