diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/LoadEntityGraphWithCompositeKeyCollectionsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/LoadEntityGraphWithCompositeKeyCollectionsTest.java new file mode 100644 index 000000000000..c9b7e3a11bea --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/LoadEntityGraphWithCompositeKeyCollectionsTest.java @@ -0,0 +1,160 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.graphs.embeddedid; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; +import org.hibernate.graph.GraphSemantic; +import org.hibernate.orm.test.jpa.graphs.embeddedid.entities.Activity; +import org.hibernate.orm.test.jpa.graphs.embeddedid.entities.ActivityAnswer; +import org.hibernate.orm.test.jpa.graphs.embeddedid.entities.ActivityAnswerId; +import org.hibernate.orm.test.jpa.graphs.embeddedid.entities.ActivityDocument; +import org.hibernate.orm.test.jpa.graphs.embeddedid.entities.ActivityDocumentId; +import org.hibernate.orm.test.jpa.graphs.embeddedid.entities.ActivityExerciseId; +import org.hibernate.orm.test.jpa.graphs.embeddedid.entities.Exercise; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Jpa( + annotatedClasses = { + Exercise.class, + Activity.class, + ActivityExerciseId.class, + ActivityAnswer.class, + ActivityAnswerId.class, + ActivityDocument.class, + ActivityDocumentId.class + } +) + +@JiraKey(value = "HHH-19137") +public class LoadEntityGraphWithCompositeKeyCollectionsTest { + + @AfterEach + public void tearDown(EntityManagerFactoryScope scope) { + scope.releaseEntityManagerFactory(); + } + + @Test + void testLoadFromEntityWithAllCollectionsFilled(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + Activity activityWithAnswersAndDocuments = createActivity(); + + ActivityAnswer activityAnswer1 = createActivityAnswer( + activityWithAnswersAndDocuments, "question_01", + "answer_01" ); + ActivityAnswer activityAnswer2 = createActivityAnswer( + activityWithAnswersAndDocuments, "question_02", + "answer_02" ); + + Set answers = new HashSet<>(); + answers.add( activityAnswer1 ); + answers.add( activityAnswer2 ); + activityWithAnswersAndDocuments.setAnswers( answers ); + + Set documents = new HashSet<>(); + documents.add( createActivityDocument( activityWithAnswersAndDocuments, "question_01", "document_01" ) ); + activityWithAnswersAndDocuments.setDocuments( documents ); + + entityManager.persist( activityWithAnswersAndDocuments ); + } + ); + + scope.inTransaction( entityManager -> { + + List activities = buildQuery( entityManager ).getResultList(); + + assertEquals( 1, activities.size() ); + assertEquals( 2, activities.get( 0 ).getAnswers().size() ); + assertEquals( 1, activities.get( 0 ).getDocuments().size() ); + + } ); + } + + @Test + void testLoadFromEntityWithOneEmptyCollection(EntityManagerFactoryScope scope) { + System.out.println( "!! one empty collection" ); + scope.inTransaction( entityManager -> { + Activity activityWithoutDocuments = createActivity(); + + ActivityAnswer activityAnswer1 = createActivityAnswer( activityWithoutDocuments, "question_01", + "answer_01" ); + ActivityAnswer activityAnswer2 = createActivityAnswer( activityWithoutDocuments, "question_02", + "answer_02" ); + + Set answers = new HashSet<>(); + answers.add( activityAnswer1 ); + answers.add( activityAnswer2 ); + activityWithoutDocuments.setAnswers( answers ); + + entityManager.persist( activityWithoutDocuments ); + } + ); + + scope.inTransaction( entityManager -> { + List activities = buildQuery( entityManager ).getResultList(); + + assertEquals( 1, activities.size() ); + assertEquals( 2, activities.get( 0 ).getAnswers().size() ); + assertEquals( 0, activities.get( 0 ).getDocuments().size() ); + } ); + } + + private TypedQuery buildQuery(EntityManager entityManager) { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Activity.class ); + + Root root = query.from( Activity.class ); + query.select( root ) + .where( builder.equal( root.get( "activityExerciseId" ).get( "exerciseId" ), 1 ) ); + + TypedQuery typedQuery = entityManager.createQuery( query ); + String graphType = GraphSemantic.LOAD.getJakartaHintName(); + String entityGraphName = "with.collections"; + typedQuery.setHint( graphType, entityManager.getEntityGraph( entityGraphName ) ); + + return typedQuery; + } + + private Activity createActivity() { + Exercise exercise = new Exercise(); + Activity activity = new Activity(); + ActivityExerciseId activityExerciseId = new ActivityExerciseId(); + activityExerciseId.setExerciseId( exercise.getId() ); + activityExerciseId.setActivityId( "general-ref" ); + activity.setExercise( exercise ).setActivityExerciseId( activityExerciseId ); + return activity; + } + + private ActivityAnswer createActivityAnswer(Activity activity, String questionId, String answer) { + ActivityAnswer newAnswer = new ActivityAnswer(); + ActivityAnswerId answerId = new ActivityAnswerId(); + answerId.setActivity( activity ).setQuestionId( questionId ); + newAnswer.setActivityAnswerId( answerId ); + newAnswer.setAnswer( answer ); + return newAnswer; + } + + private ActivityDocument createActivityDocument(Activity activity, String questionId, String name) { + ActivityDocument newDocument = new ActivityDocument(); + ActivityDocumentId documentId = new ActivityDocumentId(); + documentId.setActivity( activity ).setQuestionId( questionId ); + newDocument.setActivityDocumentId( documentId ); + newDocument.setName( name ); + return newDocument; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/Activity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/Activity.java new file mode 100644 index 000000000000..05cd0b440507 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/Activity.java @@ -0,0 +1,88 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.graphs.embeddedid.entities; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapsId; +import jakarta.persistence.NamedAttributeNode; +import jakarta.persistence.NamedEntityGraph; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +@Entity +@Table(name = "activities") +@NamedEntityGraph(name = "with.collections", + attributeNodes = { + @NamedAttributeNode(value = "answers"), + @NamedAttributeNode(value = "documents") + } +) +public class Activity { + + @EmbeddedId + private ActivityExerciseId activityExerciseId; + + @MapsId("exerciseId") + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "exercise_id") + private Exercise exercise; + + @OneToMany(mappedBy = "activityAnswerId.activity", cascade = CascadeType.ALL) + private Set answers = new HashSet<>(); + + @OneToMany(mappedBy = "activityDocumentId.activity", orphanRemoval = true, cascade = CascadeType.ALL) + private Set documents = new HashSet<>(); + + public Activity setActivityExerciseId(ActivityExerciseId activityExerciseId) { + this.activityExerciseId = activityExerciseId; + return this; + } + + public Set getAnswers() { + return answers; + } + + public Set getDocuments() { + return documents; + } + + public Activity setExercise(Exercise exercise) { + this.exercise = exercise; + return this; + } + + public Activity setAnswers(Set answers) { + this.answers = answers; + return this; + } + + public Activity setDocuments(Set documents) { + this.documents = documents; + return this; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Activity that = (Activity) o; + return Objects.equals( activityExerciseId, that.activityExerciseId ); + } + + @Override + public int hashCode() { + return Objects.hashCode( activityExerciseId ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityAnswer.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityAnswer.java new file mode 100644 index 000000000000..a0176bfcca77 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityAnswer.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.graphs.embeddedid.entities; + +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +import java.util.Objects; + +@Entity +@Table(name = "activity_answers") +public class ActivityAnswer { + + @EmbeddedId + private ActivityAnswerId activityAnswerId; + + private String answer; + + public ActivityAnswer setActivityAnswerId(ActivityAnswerId activityAnswerId) { + this.activityAnswerId = activityAnswerId; + return this; + } + + public ActivityAnswer setAnswer(String answer) { + this.answer = answer; + return this; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + ActivityAnswer that = (ActivityAnswer) o; + return Objects.equals( activityAnswerId, that.activityAnswerId ); + } + + @Override + public int hashCode() { + return Objects.hashCode( activityAnswerId ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityAnswerId.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityAnswerId.java new file mode 100644 index 000000000000..9953d4611267 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityAnswerId.java @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.graphs.embeddedid.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinColumns; +import jakarta.persistence.ManyToOne; + +import java.util.Objects; + +@Embeddable +public class ActivityAnswerId { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "exercise_id", referencedColumnName = "exercise_id"), + @JoinColumn(name = "activity_id", referencedColumnName = "activity_id") + }) + private Activity activity; + + @Column(name = "question_id") + private String questionId; + + public ActivityAnswerId() { + } + + public ActivityAnswerId setActivity(Activity activity) { + this.activity = activity; + return this; + } + + public ActivityAnswerId setQuestionId(String questionId) { + this.questionId = questionId; + return this; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + ActivityAnswerId that = (ActivityAnswerId) o; + return Objects.equals( activity, that.activity ) && Objects.equals( questionId, that.questionId ); + } + + @Override + public int hashCode() { + return Objects.hash( activity, questionId ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityDocument.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityDocument.java new file mode 100644 index 000000000000..681aa591e567 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityDocument.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.graphs.embeddedid.entities; + +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +import java.util.Objects; + +@Entity +@Table(name = "activity_documents") +public class ActivityDocument { + + @EmbeddedId + private ActivityDocumentId activityDocumentId; + + private String name; + + public ActivityDocument setActivityDocumentId(ActivityDocumentId activityDocumentId) { + this.activityDocumentId = activityDocumentId; + return this; + } + + public ActivityDocument setName(String name) { + this.name = name; + return this; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + ActivityDocument that = (ActivityDocument) o; + return Objects.equals( activityDocumentId, that.activityDocumentId ); + } + + @Override + public int hashCode() { + return Objects.hashCode( activityDocumentId ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityDocumentId.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityDocumentId.java new file mode 100644 index 000000000000..e66fe64cfeee --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityDocumentId.java @@ -0,0 +1,58 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.graphs.embeddedid.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinColumns; +import jakarta.persistence.ManyToOne; + +import java.io.Serializable; +import java.util.Objects; + +@Embeddable +public class ActivityDocumentId implements Serializable { + + private static final long serialVersionUID = 4734553376592932804L; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "exercise_id", referencedColumnName = "exercise_id"), + @JoinColumn(name = "activity_id", referencedColumnName = "activity_id") + }) + private Activity activity; + + @Column(name = "question_id") + private String questionId; + + public ActivityDocumentId() { + } + + public ActivityDocumentId setActivity(Activity activity) { + this.activity = activity; + return this; + } + + public ActivityDocumentId setQuestionId(String questionId) { + this.questionId = questionId; + return this; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + ActivityDocumentId that = (ActivityDocumentId) o; + return Objects.equals( activity, that.activity ) && Objects.equals( questionId, that.questionId ); + } + + @Override + public int hashCode() { + return Objects.hash( activity, questionId ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityExerciseId.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityExerciseId.java new file mode 100644 index 000000000000..8ecc7bbb415b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/ActivityExerciseId.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.graphs.embeddedid.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; + +import java.util.Objects; + +@Embeddable +public class ActivityExerciseId { + + private Integer exerciseId; + + @Column(name = "activity_id") + private String activityId; + + public ActivityExerciseId() { + } + + public ActivityExerciseId setExerciseId(Integer exerciseId) { + this.exerciseId = exerciseId; + return this; + } + + public ActivityExerciseId setActivityId(String activityId) { + this.activityId = activityId; + return this; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + ActivityExerciseId that = (ActivityExerciseId) o; + return Objects.equals( exerciseId, that.exerciseId ) && Objects.equals( activityId, + that.activityId ); + } + + @Override + public int hashCode() { + return Objects.hash( exerciseId, activityId ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/Exercise.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/Exercise.java new file mode 100644 index 000000000000..3c39a3bb62e5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/graphs/embeddedid/entities/Exercise.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.graphs.embeddedid.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; + +import java.util.Objects; + +@Entity +@Table(name = "exercises") +public class Exercise { + + @Id + @Column(name = "exercise_id") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "exercises_sequence") + @SequenceGenerator( + name = "exercises_sequence", + sequenceName = "exercises_sequence", + allocationSize = 1 + ) + + private Integer id; + + public Integer getId() { + return id; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Exercise exercise = (Exercise) o; + return Objects.equals( id, exercise.id ); + } + + @Override + public int hashCode() { + return Objects.hashCode( id ); + } +}