Skip to content

Commit 2145c56

Browse files
Christiaan Rudolfsdreab8
authored andcommitted
HHH-19137 - @NamedEntityGraph entity loading fails with AssertionError on entity model with multiple collections and composite keys
1 parent 4e3dfc8 commit 2145c56

File tree

1 file changed

+353
-0
lines changed

1 file changed

+353
-0
lines changed
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.jpa.graphs;
6+
7+
import jakarta.persistence.CascadeType;
8+
import jakarta.persistence.Column;
9+
import jakarta.persistence.Embeddable;
10+
import jakarta.persistence.EmbeddedId;
11+
import jakarta.persistence.Entity;
12+
import jakarta.persistence.EntityManager;
13+
import jakarta.persistence.FetchType;
14+
import jakarta.persistence.Id;
15+
import jakarta.persistence.JoinColumn;
16+
import jakarta.persistence.JoinColumns;
17+
import jakarta.persistence.ManyToOne;
18+
import jakarta.persistence.MapsId;
19+
import jakarta.persistence.NamedAttributeNode;
20+
import jakarta.persistence.NamedEntityGraph;
21+
import jakarta.persistence.OneToMany;
22+
import jakarta.persistence.Table;
23+
import jakarta.persistence.TypedQuery;
24+
import jakarta.persistence.criteria.CriteriaBuilder;
25+
import jakarta.persistence.criteria.CriteriaQuery;
26+
import jakarta.persistence.criteria.Root;
27+
import org.hibernate.SessionFactory;
28+
import org.hibernate.graph.GraphSemantic;
29+
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
30+
import org.hibernate.testing.orm.junit.JiraKey;
31+
import org.hibernate.testing.orm.junit.Jpa;
32+
import org.junit.jupiter.api.AfterEach;
33+
import org.junit.jupiter.api.Test;
34+
35+
import java.util.HashSet;
36+
import java.util.List;
37+
import java.util.Objects;
38+
import java.util.Set;
39+
40+
import static org.junit.jupiter.api.Assertions.assertEquals;
41+
42+
@Jpa(annotatedClasses = {LoadEntityGraphWithCompositeKeyCollectionsTest.Exercise.class, LoadEntityGraphWithCompositeKeyCollectionsTest.Activity.class, LoadEntityGraphWithCompositeKeyCollectionsTest.ActivityAnswer.class, LoadEntityGraphWithCompositeKeyCollectionsTest.ActivityDocument.class,})
43+
44+
@JiraKey(value = "HHH-19137")
45+
public class LoadEntityGraphWithCompositeKeyCollectionsTest {
46+
47+
@AfterEach
48+
public void tearDown(EntityManagerFactoryScope scope) {
49+
scope.getEntityManagerFactory().unwrap( SessionFactory.class ).getSchemaManager().truncateMappedObjects();
50+
scope.releaseEntityManagerFactory();
51+
}
52+
53+
@Test
54+
void testLoadFromEntityWithAllCollectionsFilled(EntityManagerFactoryScope scope) {
55+
Integer exerciseId = scope.fromTransaction( entityManager -> {
56+
Activity activityWithAnswersAndDocuments = createActivity();
57+
58+
ActivityAnswer activityAnswer1 = new ActivityAnswer(
59+
activityWithAnswersAndDocuments,
60+
"question_01",
61+
"answer_01" );
62+
ActivityAnswer activityAnswer2 = new ActivityAnswer(
63+
activityWithAnswersAndDocuments,
64+
"question_02",
65+
"answer_02" );
66+
67+
Set<ActivityAnswer> answers = new HashSet<>();
68+
answers.add( activityAnswer1 );
69+
answers.add( activityAnswer2 );
70+
activityWithAnswersAndDocuments.setAnswers( answers );
71+
72+
Set<ActivityDocument> documents = new HashSet<>();
73+
documents.add( new ActivityDocument( activityWithAnswersAndDocuments, "question_01", "document_01" ) );
74+
activityWithAnswersAndDocuments.setDocuments( documents );
75+
76+
entityManager.persist( activityWithAnswersAndDocuments );
77+
return activityWithAnswersAndDocuments.getExercise().getId();
78+
} );
79+
80+
scope.inTransaction( entityManager -> {
81+
List<Activity> activities = buildQuery( entityManager, exerciseId ).getResultList();
82+
83+
assertEquals( 1, activities.size() );
84+
assertEquals( 2, activities.get( 0 ).getAnswers().size() );
85+
assertEquals( 1, activities.get( 0 ).getDocuments().size() );
86+
87+
} );
88+
}
89+
90+
@Test
91+
void testLoadFromEntityWithOneEmptyCollection(EntityManagerFactoryScope scope) {
92+
Integer exerciseId = scope.fromTransaction( entityManager -> {
93+
Activity activityWithoutDocuments = createActivity();
94+
95+
ActivityAnswer activityAnswer1 = new ActivityAnswer( activityWithoutDocuments, "question_01", "answer_01" );
96+
ActivityAnswer activityAnswer2 = new ActivityAnswer( activityWithoutDocuments, "question_02", "answer_02" );
97+
98+
Set<ActivityAnswer> answers = new HashSet<>();
99+
answers.add( activityAnswer1 );
100+
answers.add( activityAnswer2 );
101+
activityWithoutDocuments.setAnswers( answers );
102+
103+
entityManager.persist( activityWithoutDocuments );
104+
return activityWithoutDocuments.getExercise().getId();
105+
106+
} );
107+
108+
scope.inTransaction( entityManager -> {
109+
List<Activity> activities = buildQuery( entityManager, exerciseId ).getResultList();
110+
111+
assertEquals( 1, activities.size() );
112+
assertEquals( 2, activities.get( 0 ).getAnswers().size() );
113+
assertEquals( 0, activities.get( 0 ).getDocuments().size() );
114+
} );
115+
}
116+
117+
private TypedQuery<Activity> buildQuery(EntityManager entityManager, Integer exerciseId) {
118+
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
119+
CriteriaQuery<Activity> query = builder.createQuery( Activity.class );
120+
121+
Root<Activity> root = query.from( Activity.class );
122+
query.select( root ).where( builder.equal( root.get( "activityExerciseId" ).get( "exerciseId" ), exerciseId ) );
123+
124+
TypedQuery<Activity> typedQuery = entityManager.createQuery( query );
125+
String graphType = GraphSemantic.LOAD.getJakartaHintName();
126+
String entityGraphName = "with.collections";
127+
typedQuery.setHint( graphType, entityManager.getEntityGraph( entityGraphName ) );
128+
129+
return typedQuery;
130+
}
131+
132+
private Activity createActivity() {
133+
return new Activity( new Exercise( 1, "Pull up" ), "general-ref" );
134+
}
135+
136+
@Entity(name = "Activity")
137+
@Table(name = "activities")
138+
@NamedEntityGraph(name = "with.collections",
139+
attributeNodes = {@NamedAttributeNode(value = "answers"), @NamedAttributeNode(value = "documents")})
140+
public static class Activity {
141+
142+
@EmbeddedId
143+
private ActivityExerciseId activityExerciseId;
144+
145+
@MapsId("exerciseId")
146+
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
147+
@JoinColumn(name = "exercise_id")
148+
private Exercise exercise;
149+
150+
@OneToMany(mappedBy = "activityAnswerId.activity", cascade = CascadeType.ALL)
151+
private Set<ActivityAnswer> answers = new HashSet<>();
152+
153+
@OneToMany(mappedBy = "activityDocumentId.activity", orphanRemoval = true, cascade = CascadeType.ALL)
154+
private Set<ActivityDocument> documents = new HashSet<>();
155+
156+
public Activity() {
157+
}
158+
159+
public Activity(Exercise exercise, ActivityExerciseId activityExerciseId) {
160+
this.exercise = exercise;
161+
this.activityExerciseId = activityExerciseId;
162+
}
163+
164+
public Activity(Exercise exercise, String activityId) {
165+
this( exercise, new ActivityExerciseId( exercise.getId(), activityId ) );
166+
}
167+
168+
public Exercise getExercise() {
169+
return exercise;
170+
}
171+
172+
public Set<ActivityAnswer> getAnswers() {
173+
return answers;
174+
}
175+
176+
public Set<ActivityDocument> getDocuments() {
177+
return documents;
178+
}
179+
180+
public void setAnswers(Set<ActivityAnswer> answers) {
181+
this.answers = answers;
182+
}
183+
184+
public void setDocuments(Set<ActivityDocument> documents) {
185+
this.documents = documents;
186+
}
187+
}
188+
189+
@Embeddable
190+
public static class ActivityExerciseId {
191+
192+
private Integer exerciseId;
193+
194+
@Column(name = "activity_id")
195+
private String activityId;
196+
197+
public ActivityExerciseId() {
198+
}
199+
200+
public ActivityExerciseId(Integer exerciseId, String activityId) {
201+
this.exerciseId = exerciseId;
202+
this.activityId = activityId;
203+
}
204+
205+
@Override
206+
public boolean equals(Object o) {
207+
if ( o == null || getClass() != o.getClass() ) {
208+
return false;
209+
}
210+
ActivityExerciseId that = (ActivityExerciseId) o;
211+
return Objects.equals( exerciseId, that.exerciseId ) && Objects.equals( activityId, that.activityId );
212+
}
213+
214+
@Override
215+
public int hashCode() {
216+
return Objects.hash( exerciseId, activityId );
217+
}
218+
}
219+
220+
@Entity(name = "Exercise")
221+
@Table(name = "exercises")
222+
public static class Exercise {
223+
224+
@Id
225+
private Integer id;
226+
227+
private String name;
228+
229+
public Exercise() {
230+
}
231+
232+
public Exercise(Integer id, String name) {
233+
this.id = id;
234+
this.name = name;
235+
}
236+
237+
public Integer getId() {
238+
return id;
239+
}
240+
}
241+
242+
@Entity(name = "ActivityAnswer")
243+
@Table(name = "activity_answers")
244+
public static class ActivityAnswer {
245+
246+
@EmbeddedId
247+
private ActivityAnswerId activityAnswerId;
248+
249+
private String answer;
250+
251+
public ActivityAnswer() {
252+
}
253+
254+
public ActivityAnswer(ActivityAnswerId activityAnswerId, String answer) {
255+
this.activityAnswerId = activityAnswerId;
256+
this.answer = answer;
257+
}
258+
259+
public ActivityAnswer(Activity activity, String questionId, String answer) {
260+
this( new ActivityAnswerId( activity, questionId ), answer );
261+
}
262+
}
263+
264+
@Embeddable
265+
public static class ActivityAnswerId {
266+
267+
@ManyToOne(fetch = FetchType.LAZY)
268+
@JoinColumns({@JoinColumn(name = "exercise_id", referencedColumnName = "exercise_id"), @JoinColumn(
269+
name = "activity_id", referencedColumnName = "activity_id")})
270+
private Activity activity;
271+
272+
@Column(name = "question_id")
273+
private String questionId;
274+
275+
public ActivityAnswerId() {
276+
}
277+
278+
public ActivityAnswerId(Activity activity, String questionId) {
279+
this.activity = activity;
280+
this.questionId = questionId;
281+
}
282+
283+
@Override
284+
public boolean equals(Object o) {
285+
if ( o == null || getClass() != o.getClass() ) {
286+
return false;
287+
}
288+
ActivityAnswerId that = (ActivityAnswerId) o;
289+
return Objects.equals( activity, that.activity ) && Objects.equals( questionId, that.questionId );
290+
}
291+
292+
@Override
293+
public int hashCode() {
294+
return Objects.hash( activity, questionId );
295+
}
296+
}
297+
298+
@Entity(name = "ActivityDocument")
299+
@Table(name = "activity_documents")
300+
public static class ActivityDocument {
301+
302+
@EmbeddedId
303+
private ActivityDocumentId activityDocumentId;
304+
305+
private String name;
306+
307+
public ActivityDocument() {
308+
}
309+
310+
public ActivityDocument(ActivityDocumentId activityDocumentId, String name) {
311+
this.activityDocumentId = activityDocumentId;
312+
this.name = name;
313+
}
314+
315+
public ActivityDocument(Activity activity, String questionId, String name) {
316+
this( new ActivityDocumentId( activity, questionId ), name );
317+
}
318+
}
319+
320+
@Embeddable
321+
public static class ActivityDocumentId {
322+
323+
@ManyToOne(fetch = FetchType.LAZY)
324+
@JoinColumns({@JoinColumn(name = "exercise_id", referencedColumnName = "exercise_id"), @JoinColumn(
325+
name = "activity_id", referencedColumnName = "activity_id")})
326+
private Activity activity;
327+
328+
@Column(name = "question_id")
329+
private String questionId;
330+
331+
public ActivityDocumentId() {
332+
}
333+
334+
public ActivityDocumentId(Activity activity, String questionId) {
335+
this.activity = activity;
336+
this.questionId = questionId;
337+
}
338+
339+
@Override
340+
public boolean equals(Object o) {
341+
if ( o == null || getClass() != o.getClass() ) {
342+
return false;
343+
}
344+
ActivityDocumentId that = (ActivityDocumentId) o;
345+
return Objects.equals( activity, that.activity ) && Objects.equals( questionId, that.questionId );
346+
}
347+
348+
@Override
349+
public int hashCode() {
350+
return Objects.hash( activity, questionId );
351+
}
352+
}
353+
}

0 commit comments

Comments
 (0)